字符串
检测大写字母
给定一个单词,你需要判断单词的大写使用是否正确。
我们定义,在以下情况时,单词的大写用法是正确的:
全部字母都是大写,比如"USA"。
单词中所有字母都不是大写,比如"leetcode"。
如果单词不只含有一个字母,只有首字母大写, 比如 "Google"。
class Solution:
def detectCapitalUse(self, word: str) -> bool:
if word[0].islower():
if word.islower():
return True
else:
if word.isupper():
return True
elif word[1:].islower():
return True
return False
考虑三种情况,如果用java就比较啰嗦。灵活使用python内置的语法。
链表
链表题目的考虑重点,是否可以用递归。链表与递归之间是有很大关系的,如:
删除排序链表中的重复元素
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//空列表或者只有一个节点的列表
if(head == null || head.next == null) return head;
if(head.val == head.next.val){
//头结点需要删除,以第一个不重复的节点为头节点
int tmp = head.val;
while(head!=null && head.val==tmp)
{
head = head.next;
}
return deleteDuplicates(head);
}else{
//如果头结点不需要删除
head.next = deleteDuplicates(head.next);
return head;
}
}
}
-
找终止条件:当head指向链表只剩一个元素的时候,自然是不可能重复的,因此return
-
想想应该返回什么值:应该返回的自然是已经去重的链表的头节点
-
每一步要做什么:宏观上考虑,此时head.next已经指向一个去重的链表了,而根据第二步,我应该返回一个去重的链表的头节点。因此这一步应该做的是判断当前的head和head.next是否相等,如果相等则说明重了,返回head.next,否则返回head
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null){
return head;
}
head.next = deleteDuplicates(head.next);
if(head.val == head.next.val)
head = head.next;
return head;
}
}
// 要深刻理解递归!
判断链表中是否存在环
思路
我们可以通过检查一个结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表。
算法
我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
// 哈希集合Set
Set<ListNode> nodesSeen = new HashSet<>();
while(head != null){
if (nodesSeen.contains(head))
return true;
else{
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
}
这种解法中注意哈希表在Java中的使用:Set<ListNode> nodesSeen = new HashSet<>();
这种解法的时间复杂度是O(n),空间复杂度也是O(n)。每个节点最多访问一次,哈希表中最多添加n个节点元素。
另一种解法是可以用快慢指针:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null)
return false;
ListNode slow = head;
ListNode fast = head.next;
while(fast != slow){
//这个地方注意判断fast.next的值,因为下面代码中有fast.next.next的使用
if (fast == null || fast.next == null){
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
为什么快慢指针是正确的?怎么证明?
因为只要有环,那么快指针是一定能够赶得上慢指针的。有环会进入一个循环中,链表的访问不会结束。
反转链表
如何原地反转链表。反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//迭代的方法
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null; //当前的前指针
ListNode curr = head; //当前节点
//每次循环,都将当前节点指向它前面的节点,然后当前节点和签节点后移
while(curr!=null)
{
ListNode nextTemp = curr.next;
curr.next = prev; //curr指向prev节点
prev = curr;
curr = nextTemp;
}
return prev;
}
}
//递归的方法
class Solution {
//将链表分为head 和 new_head两部分。假设new_head是已经反转好的链表,只是考虑head和new_head两个节点。
// 1. head.next要设置为null
// 2. head.next指向head本身
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null) return head;
ListNode new_head = reverseList(head.next);
head.next.next = head; //反转
head.next = null;
return new_head;
}
}
删除链表中指定val的所有节点
示例:
输入:1->2->6->3->4->5->6, val=6
输出:1->2->3->4->5
考虑两种思路:普通思路和递归思路。普通解法要设置一下新的头节点,保持算法一致性避免特殊处理。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//递归解法,很是精妙!
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) return head;
head.next = removeElements(head.next, val);
return head.val == val ? head.next: head;
}
}
上述解法真的很巧妙。如果head为null,则直接返回head;其他调用递归。返回时,如果head.val为目标值,则返回head.next。
// 普通解法
public ListNode removeElements(ListNode head, int val) {
// 先创建一个Node节点,避免影响首节点
ListNode header = new ListNode(-1);
header.next = head;
ListNode cur = header;
while(cur.next != null) {
if (cur.next.val == val) {
// 直接跳过目标节点
cur.next = cur.next.next;
}
else{
cur = cur.next;
}
}
return header.next;
}
数字
判断是否是丑数
class Solution {
public boolean isUgly(int num) {
if(num == 0){
return false;
}
while (num != 1){
if(num % 2 == 0){
num /= 2;
//continue;
}
else if(num % 3 == 0){
num /= 3;
// continue;
}
else if(num % 5 == 0){
num /= 5;
//continue;
}
else
return false;
}
return true;
}
}
简单写法:
public boolean isUgly(int num){
while(num%2 == 0) num = num/2;
while(num%3 == 0) num = num/3;
while(num%5 == 0) num = num/5;
if (num == 1) return true;
return false;
}
第n个丑数
【笔记】使用三指针法。一部分是丑数数组,另一部分是权重2,3,5。下一个丑数,定义为丑数数组中的数乘以权重,所得的最小值。
那么,2该乘以谁?3该乘以谁?5该乘以谁?
class Solution:
def nthUglyNumber(self, n: int) -> int:
res = [1]
idx2 = 0
idx3 = 0
idx5 = 0
for i in range(n-1):
res.append(min(res[idx2]*2, res[idx3]*3, res[idx5]*5))
# 如果最后一个值来自某一个
if res[-1] == res[idx2]*2:
idx2 += 1
if res[-1] == res[idx3]*3:
idx3 += 1
if res[-1] == res[idx5]*5:
idx5 += 1
return res[-1]
充分利用了2、3、5三个数值,因为丑数就是由这三部分组成的。注意:range(n-1),为0 到 n-2,加上原来已经有的1,一共有n-1个值。
栈
栈的应用在于它是一个先进后出的数据结构,可以利用这个特性做一些事情。
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(') {
stack.push(')');
continue;
}
if (c == '[') {
stack.push(']');
continue;
}
if (c == '{') {
stack.push('}');
continue;
}
if (stack.empty() || stack.pop() != c) {
return false;
}
}
return stack.empty();
}
}
这道题目中考虑到java中栈这个数据结构的使用。Stack
二叉树
判断二叉树是否相同
二叉树相同的条件:
- 根节点相同
- 左子树相同
- 右子树相同
第一时间的想法肯定是用递归思想。如果root相同,则比较左子树和右子树,全部相同则是相同的二叉树。一次编译通过。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null || q == null) {
if (p == null && q == null)
return true;
else{
return false;
}
}
if (p.val != q. val){
return false;
}
else{
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
}
判断是否是对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean compareTree(TreeNode l, TreeNode r){
if(l==null && r==null) return true;
if(l==null || r==null) return false;
if(l.val != r.val) return false;
return compareTree(l.left, r.right) && compareTree(l.right, r.left);
}
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return compareTree(root.left, root.right);
}
}
定义了内部一个函数compareTree,传入参数是两个树。
计算二叉树的最大深度
使用递归的算法,其实很简单。递归可以解决很多看上去很复杂的问题。
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
return Math.max(maxDepth(root.left)+1, maxDepth(root.right)+1);
}
}
难以置信的简单。
计算二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最小深度 2.
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
// 特殊场景
if (root == null) return 0;
if(root.left == null && root.right==null) return 1;
int min_depth = Integer.MAX_VALUE;
if (root.left != null) {
min_depth = Math.min(minDepth(root.left), min_depth);
}
if (root.right != null) {
min_depth = Math.min(minDepth(root.right), min_depth);
}
return min_depth+1;
}
}
翻转二叉树
如何翻转二叉树。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//先序遍历 --- 从顶向下交换
public TreeNode invertTree(TreeNode root) {
if (root==null) return root;
// 保存右子树
TreeNode rightTree = root.right;
// 交换左右子树的位置
root.right = invertTree(root.left);
root.left = invertTree(rightTree);
return root;
}
}
计算二叉树的高度
计算二叉树的高度,考虑清楚递归边界条件:
-
Node本身为空。
-
Node的左右子节点均为空。
public int dfs(TreeNode x){
if(x == null) return 0;
if(x.left == null && x.right == null) return 1;
int leftH = dfs(x.left)+1;
int rightH = dfs(x.right)+1;
return Math.max(leftH, rightH);
}
先明确特殊情况:空树,单节点树。
然后递归调用函数本身,返回左右子树的最大高度。
判断二叉树是否是平衡二叉树
递归求解每个节点左右子树的高度。
public boolean isBalanced(TreeNode root){
if (root == null) return true;
// 判断左右子树是否是平衡二叉树
if(!isBalanced(root.left)) return false;
if(!isBalanced(root.right)) return false;
//分别计算左右子树的高度
int leftH = dfs(root.left) + 1;
int rightH = dfs(root.right) + 1;
if(Math.abs(leftH - rightH) > 1) {
return false;
}
else{
return true;
}
}
中序遍历二叉树
中序是指root根节点来说的,root节点是中间被加入的。
一般使用递归方法比较常见。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null) return list;
inorderTraversal(root.left);
list.add(root.val);
inorderTraversal(root.right);
return list;
}
}
python的实现:
以下两种写法都是正确的。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res_list = []
if root:
res_list += self.inorderTraversal(root.left)
res_list.append(root.val)
res_list += self.inorderTraversal(root.right)
return res_list
def inorder_traversal(p):
if not p:
return []
res = []
res.extend(inorder_traversal(p.left))
res.append(p.val)
res.extend(inorder_traveral(p.right))
return res
非递归的实现:
def inorder_traversal(root):
stack = []
res = []
if not root:
return []
while root or stack:
# 先把左子树加入stack
while root:
stack.append(root)
root = root.left
if stack:
a = stack.pop()
res.append(a.val)
root = a.right
return res
前序遍历二叉树
先访问root根节点,再访问左子树和右子树。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res_list = []
if not root:
return res_list
res_list.append(root.val)
res_list += self.preorderTraversal(root.left)
res_list += self.preorderTraversal(root.right)
return res_list
java的实现:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root == null) return list;
list.add(root.val);
preorderTraversal(root.left);
preorderTraversal(root.right);
return list;
}
}
非递归的实现:
非递归的时候stack必须先加入右子树节点再加入左子树节点,因为stack的机制是先入后出。
def preOrderTravese(root):
stack = [root]
res = []
while stack:
res.append(root.val)
if root.right:
stack.append(root.right)
if root.left:
stack.append(root.left)
root = stack.pop()
层次遍历二叉树
按层遍历:从上到下、从左到右按层遍历
利用队列这个数据结构。
# 先进先出选用队列结构
import queue
def layerTraverse(head):
if not head:
return None
que = queue.Queue() # 创建先进先出队列
que.put(head)
while not que.empty():
head = que.get() # 弹出第一个元素并打印
print(head.val)
if head.left: # 若该节点存在左子节点,则加入队列(先push左节点)
que.put(head.left)
if head.right: # 若该节点存在右子节点,则加入队列(再push右节点)
que.put(head.right)
二叉树节点个数
# 求二叉树节点个数
def tree_node_nums(node):
if node is None:
return 0
nums = tree_node_nums(node.left)
nums += tree_node_nums(node.right)
return nums + 1
最后的加1是因为算上了节点本身。
哈希表
判断两个字符串是否互为变形词
变形词,出现的字符种类一样,并且每种字符出现的次数也一样。
// 时间复杂度为O(N), 空间复杂度为O(M)
public static boolean isDeformation(String str1, String str2) {
if(str1 == null || str2 == null || str1.length() != str2.length()) {
return false;
}
char[] chas1 = str1.toCharArray();
char[] chas2 = str2.toCharArray();
int[] map = new int[256];
for(int i=0; i<chas1.length; i++) {
map[chas1[i]] ++;
}
for(int j=0; j<chas2.length;j++) {
//如果减少之前的值小于0,则说直接返回false
//如果遍历完str2,map中的值也没有出现负值,则返回true
if(map[chas2[j]] == 0) {
return false;
}
map[chas2[j]]--;
}
return true;
}
巧妙地使用了数组,因为字符的数字在256之内。
数组
合并排序的数组
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。初始化 A 和 B 的元素数量分别为 m 和 n。
class Solution {
// 使用尾插法
public void merge(int[] A, int m, int[] B, int n) {
int i =m-1, j=n-1, idx = m+n-1;
while(j>=0){
if(i<0 || B[j]>=A[i]){
A[idx--] = B[j--];
}
else{
A[idx--] = A[i--];
}
}
}
}
//另一个解法,也是尾插法
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
int i = m-1, j = n-1, idx = m+n-1;
while(i>=0 && j>=0){
if (A[i]<B[j]){
A[idx--] = B[j--];
}
else{
A[idx--] = A[i--];
}
}
//处理最后B剩下的,A不需要处理
while(j>=0){
A[idx--] = B[j--];
}
}
}
一个偷巧的方法,效率还很快。
class Solution:
def merge(self, A: List[int], m: int, B: List[int], n: int) -> None:
"""
Do not return anything, modify A in-place instead.
"""
A[m:m+n+1] = B
A.sort()
求众数
给定一个大小为n的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于n/2的元素。可以假设数组是非空的,并且给定的数组总是存在多数元素。
暴力法,时间为O(n^2), 空间复杂度O(1):
class Solution{
public int majorityElement(int[] nums){
int majorityCount = nums.length/2;
for(int num : nums){
int count = 0;
for (int elem : nums){
if (elem == num){
count += 1;
}
}
if (count>majorityCount){
return num;
}
}
return -1;
}
}
使用哈希表,时间复杂度为O(n),空间复杂度为O(n),相当于以空间换时间:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# 使用哈希表
nums_dict = {}
for i in nums:
if i in nums_dict:
nums_dict[i] += 1
else:
nums_dict[i] = 1
for k, v in nums_dict.items():
if v > len(nums)//2:
return k
return -1
将每个元素替换为右侧最大元素
给你一个数组 arr
,请你将每个元素用它右边最大的元素替换,如果是最后一个元素,用 -1
替换。
完成所有替换操作后,请你返回这个数组。
// 1.从后往前排
// 2.定义一个变量跟踪最大值
class Solution {
public int[] replaceElements(int[] arr) {
int max = -1;
for (int i = arr.length - 1; i >= 0; i--) {
int temp = arr[i];
arr[i] = max;
if (temp > max) {
max = temp;
}
}
return arr;
}
}
python版本,注意倒序遍历的写法:
class Solution:
def replaceElements(self, arr: List[int]) -> List[int]:
max_arr = -1
for i in range(len(arr)-1, -1, -1):
tmp = arr[i]
arr[i] = max_arr
max_arr = max(max_arr, tmp)
return arr
二分查找
寻找比目标字母大的最小字母
使用二分查找,python3中mid=(low+high)//2
class Solution:
def nextGreatestLetter(self, letters: List[str], target: str) -> str:
res = letters[0]
low = 0
high = len(letters)-1
while low <= high:
mid = (low+high)//2
if letters[mid] > target:
res = letters[mid]
high = mid - 1
else:
low = mid + 1
return res