持续更新中~~~
数组
有序数组的平方(LeetCode977)
原地算法外加双指针
class Solution {
public int[] sortedSquares(int[] nums) {
int n=nums.length;
int[] res=new int[n];
for(int i=0,j=n-1,k=n-1;i<=j;){
if(Math.abs(nums[i])<=Math.abs(nums[j])){
res[k]=nums[j]*nums[j];
j--;k--;
}
else{
res[k]=nums[i]*nums[i];
i++;k--;
}
}
return res;
}
}
长度最小的子数组(LeetCode209)
如果具备单调性,则用双指针拉框
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int res=Integer.MAX_VALUE;
for (int i=0,j=0,sum=0;i<nums.length; i ++ ) {
sum += nums[i];
while (sum-nums[j] >= target) sum-=nums[j ++ ];
if (sum >= target) res=Math.min(res, i - j + 1);
}
return res==Integer.MAX_VALUE?0:res;
}
}
螺旋矩阵||(LeetCode59)
class Solution {
public int[][] generateMatrix(int n) {
int[][] con={{0,1},{1,0},{0,-1},{-1,0}};
int[][] res= new int[n][n];
for(int i=1,x=0,y=0,k=0;i<=n*n;i++){
res[x][y]=i;
int a=x+con[k][0],b=y+con[k][1];
if(a<0||a>=n||b<0||b>=n||res[a][b]!=0){
k=(k+1)%4;
}
x+=con[k][0];
y+=con[k][1];
}
return res;
}
}
链表
设计链表(LeetCode707)
反转链表(LeetCode206)
两个指针移动就好,唯一要注意的是head.next节点要重新赋值为null
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode before=head,after=head.next;
while(after!=null){
ListNode temp=after.next;
after.next=before;
before=after;
after=temp;
}
head.next=null;
return before;
}
}
递归解法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode *tail = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return tail;
}
};
两两交换链表中的节点(LeetCode24)
头结点会变,所以用虚拟头结点,三个指针搞定
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode res=new ListNode(0);
res.next=head;
ListNode node=res;
while(node.next!=null&&node.next.next!=null){
ListNode a=node.next,b=node.next.next;
a.next=b.next;
node.next=b;
b.next=a;
node=a;
}
return res.next;
}
}
K个一组翻转链表(LeetCode25)
先判断是否有K个,先内部翻转,再将外部连接。
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode start=dummy;
while(start!=null){
ListNode end=start;
for(int i=0;i<k&&end!=null;i++) end=end.next;
if(end==null) break;
ListNode a=start.next,b=a.next;
for(int i=0;i<k-1;i++){
ListNode temp=b.next;
b.next=a;
a=b;
b=temp;
}
ListNode temp=start.next;
temp.next=b;
start.next=a;
start=temp;
}
return dummy.next;
}
}
环形链表||
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null) return null;
ListNode fast=head.next,slow=head;
while(fast!=slow){
if(fast==null&&slow==null) return null;
}
return fast;
}
}
哈希表
两个数组的交集(LeetCode349)
统计两个数组找出两个数组中均存在的元素。长度有限用数组,无限用hash,去重用hashset
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
int[] hash1 = new int[1002];
int[] hash2 = new int[1002];
for(int i : nums1)
hash1[i]++;
for(int i : nums2)
hash2[i]++;
List<Integer> resList = new ArrayList<>();
for(int i = 0; i < 1002; i++)
if(hash1[i] > 0 && hash2[i] > 0)
resList.add(i);
int index = 0;
int res[] = new int[resList.size()];
for(int i : resList)
res[index++] = i;
return res;
}
}
快乐数(LeetCode202)
按照逻辑写循环代码,终止条件为得到结果为1或出现重复项。
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();
while (n != 1 && !record.contains(n)) {
record.add(n);
n = getNextNumber(n);
}
return n == 1;
}
private int getNextNumber(int n) {
int res = 0;
while (n > 0) {
int temp = n % 10;
res += temp * temp;
n = n / 10;
}
return res;
}
}
两数之和(LeetCode1)
我是傻逼,必须一边遍历一边找,不然会有重复项搞心态
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i]; // 遍历当前元素,并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i); // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}
四数相加II(LeetCode454)
把四个数分两组,一组记录次数,另一组来与前一组匹配
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<Integer, Integer> countAB = new HashMap<Integer, Integer>();
for (int u : A) {
for (int v : B) {
countAB.put(u + v, countAB.getOrDefault(u + v, 0) + 1);
}
}
int ans = 0;
for (int u : C) {
for (int v : D) {
if (countAB.containsKey(-u - v)) {
ans += countAB.get(-u - v);
}
}
}
return ans;
}
}
三数之和(LeetCode15)
因为需要去重,所以哈希表作用不大。进行去重遍历,再用双指针找值即可
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
四数之和(LeetCode18)
比三数之和多一重for循环
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// nums[i] > target 直接返回, 剪枝操作
if (nums[i] > 0 && nums[i] > target) {
return result;
}
if (i > 0 && nums[i - 1] == nums[i]) { // 对nums[i]去重
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) { // 对nums[j]去重
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
}
字符串
反转字符串II(LeetCode541)
class Solution {
public String reverseStr(String s, int k) {
int n = s.length();
char[] arr = s.toCharArray();
for (int i = 0; i < n; i += 2 * k) {
reverse(arr, i, Math.min(i + k, n) - 1);
}
return new String(arr);
}
public void reverse(char[] arr, int left, int right) {
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
}
反转字符串中的单词(LeetCode151)
从后往前,分割单词放入队头
class Solution {
public String reverseWords(String s) {
s = s.trim(); // 删除首尾空格
int j = s.length() - 1, i = j;
StringBuilder res = new StringBuilder();
while (i >= 0) {
while (i >= 0 && s.charAt(i) != ' ') i--; // 搜索首个空格
res.append(s.substring(i + 1, j + 1) + " "); // 添加单词
while (i >= 0 && s.charAt(i) == ' ') i--; // 跳过单词间空格
j = i; // j 指向下个单词的尾字符
}
return res.toString().trim(); // 转化为字符串并返回
}
}
找出字符串中第一个匹配项的下标(LeetCode28)
KMP,朴素做法也能解但是如果是好的公司,用朴素做法容易凉
class Solution {
// KMP 算法
// ss: 原串(string) pp: 匹配串(pattern)
public int strStr(String ss, String pp) {
if (pp.isEmpty()) return 0;
// 分别读取原串和匹配串的长度
int n = ss.length(), m = pp.length();
// 原串和匹配串前面都加空格,使其下标从 1 开始
ss = " " + ss;
pp = " " + pp;
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
int[] next = new int[m + 1];
// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
for (int i = 2, j = 0; i <= m; i++) {
// 匹配不成功的话,j = next(j)
while (j > 0 && p[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++
if (p[i] == p[j + 1]) j++;
// 更新 next[i],结束本次循环,i++
next[i] = j;
}
// 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
for (int i = 1, j = 0; i <= n; i++) {
// 匹配不成功 j = next(j)
while (j > 0 && s[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++,结束本次循环后 i++
if (s[i] == p[j + 1]) j++;
// 整一段匹配成功,直接返回下标
if (j == m) return i - m;
}
return -1;
}
}
KMP代码模板:
public boolean kmp(String query, String pattern) {
int n = query.length();
int m = pattern.length();
int[] fail = new int[m];
Arrays.fill(fail, -1);
for (int i = 1; i < m; ++i) {
int j = fail[i - 1];
while (j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)) {
j = fail[j];
}
if (pattern.charAt(j + 1) == pattern.charAt(i)) {
fail[i] = j + 1;
}
}
int match = -1;
for (int i = 1; i < n - 1; ++i) {
while (match != -1 && pattern.charAt(match + 1) != query.charAt(i)) {
match = fail[match];
}
if (pattern.charAt(match + 1) == query.charAt(i)) {
++match;
if (match == m - 1) {
return true;
}
}
}
return false;
}
重复的子字符串(LeetCode459)
class Solution {
public boolean repeatedSubstringPattern(String s) {
return kmp(s + s, s);
}
public boolean kmp(String query, String pattern) {
int n = query.length();
int m = pattern.length();
int[] fail = new int[m];
Arrays.fill(fail, -1);
for (int i = 1; i < m; ++i) {
int j = fail[i - 1];
while (j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)) {
j = fail[j];
}
if (pattern.charAt(j + 1) == pattern.charAt(i)) {
fail[i] = j + 1;
}
}
int match = -1;
for (int i = 1; i < n - 1; ++i) {
while (match != -1 && pattern.charAt(match + 1) != query.charAt(i)) {
match = fail[match];
}
if (pattern.charAt(match + 1) == query.charAt(i)) {
++match;
if (match == m - 1) {
return true;
}
}
}
return false;
}
}
双指针
移除元素(LeetCode27)
双指针,左边指针找等于val的,右边指针找不等于val的。长度就是left的位置
class Solution {
public int removeElement(int[] nums, int val) {
int l=0;
int r=nums.length-1;
while(l<=r){
while(l<=r&&nums[l]!=val) l++;
while(l<=r&&nums[r]==val) r--;
if(l<r) nums[l++]=nums[r--];
}
return l;
}
}
栈与队列
有效的括号(LeetCode20)
考虑三种情况,用栈压入
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
}
}
删除字符串中所有相邻的重复项(LeetCode1047)
这道题队列,栈都可以。放在这里就是熟悉一下StringBuffer的写法
class Solution {
public String removeDuplicates(String s) {
StringBuffer stack = new StringBuffer();
int top = -1;
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
if (top >= 0 && stack.charAt(top) == ch) {
stack.deleteCharAt(top);
--top;
} else {
stack.append(ch);
++top;
}
}
return stack.toString();
}
}
二叉树
左叶子之和(LeetCode404)
遍历,难点在于如何判断该节点为左叶子节点
class Solution {
int res = 0;
public int sumOfLeftLeaves(TreeNode root) {
dfs(root);
return res;
}
public void dfs(TreeNode root) {
if (root == null) return;
if (root.left != null) {
if (root.left.left == null && root.left.right == null) {
res += root.left.val;
}
}
dfs(root.left);
dfs(root.right);
}
}
找树左下角的值(LeetCode513)
通过maxd记录深度,最左侧的值恰好是突破当前记录的最大深度的第一个值。
class Solution {
int ans=0;
int maxd=0;
public int findBottomLeftValue(TreeNode root) {
dfs(root, 1);
return ans;
}
private void dfs(TreeNode root, int d) {
if (root == null) return;
if (d > maxd) {
maxd = d;
ans = root.val;
}
dfs(root.left, d + 1);
dfs(root.right, d + 1);
}
}
二叉树的所有路径(LeetCode257)
要用到StringBuffer函数,终止条件是左右同时为null,使用中序遍历
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<String>();
constructPaths(root, "", paths);
return paths;
}
public void constructPaths(TreeNode root, String path, List<String> paths) {
if (root != null) {
StringBuffer pathSB = new StringBuffer(path);
pathSB.append(Integer.toString(root.val));
if (root.left == null && root.right == null) { // 当前节点是叶子节点
paths.add(pathSB.toString()); // 把路径加入到答案中
} else {
pathSB.append("->"); // 当前节点不是叶子节点,继续递归遍历
constructPaths(root.left, pathSB.toString(), paths);
constructPaths(root.right, pathSB.toString(), paths);
}
}
}
}
路径总和(LeetCode112)
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum == root.val;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
路径总和II(LeetCode113)
用队列,满足就插入不满足就将当前值弹出
class Solution {
List<List<Integer>> ret = new LinkedList<List<Integer>>();
Deque<Integer> path = new LinkedList<Integer>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root, targetSum);
return ret;
}
public void dfs(TreeNode root, int targetSum) {
if (root == null) {
return;
}
path.offerLast(root.val);
targetSum -= root.val;
if (root.left == null && root.right == null && targetSum == 0) {
ret.add(new LinkedList<Integer>(path));
}
dfs(root.left, targetSum);
dfs(root.right, targetSum);
path.pollLast();
}
}
最大二叉树(LeetCode654)
找到最大值,然后把最大值分为左右两块进行递归
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return construct(nums, 0, nums.length - 1);
}
public TreeNode construct(int[] nums, int left, int right) {
if (left > right) {
return null;
}
int best = left;
for (int i = left + 1; i <= right; ++i) {
if (nums[i] > nums[best]) {
best = i;
}
}
TreeNode node = new TreeNode(nums[best]);
node.left = construct(nums, left, best - 1);
node.right = construct(nums, best + 1, right);
return node;
}
}
合并二叉树(LeetCode617)
构造二叉树的基本代码
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1==null&&root2==null) return null;
if(root1==null) return root2;
if(root2==null) return root1;
TreeNode node=new TreeNode(root1.val+root2.val);
node.left=mergeTrees(root1.left,root2.left);
node.right=mergeTrees(root1.right,root2.right);
return node;
}
}
二叉搜索树中的搜索(LeetCode700)
我是傻逼
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root==null) return null;
if(root.val==val) return root;
return searchBST(val < root.val ? root.left : root.right, val);
}
}
二叉搜索树的最小绝对差(LeetCode530)
把节点的值按从小到大排序,计算最小的差值即可。因为是二叉搜索树,所有可以一边遍历一边计算,二叉搜索树一般使用中序遍历。
class Solution {
TreeNode pre;// 记录上一个遍历的结点
int result = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root==null)return 0;
traversal(root);
return result;
}
public void traversal(TreeNode root){
if(root==null)return;
traversal(root.left);
if(pre!=null){
result = Math.min(result,root.val-pre.val);
}
pre = root;
traversal(root.right);
}
}
二叉搜索树中的众数(LeetCode501)
和上一题的解法一样
class Solution {
ArrayList<Integer> resList;
int maxCount;
int count;
TreeNode pre;
public int[] findMode(TreeNode root) {
resList = new ArrayList<>();
maxCount = 0;
count = 0;
pre = null;
findMode1(root);
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
}
public void findMode1(TreeNode root) {
if (root == null) {
return;
}
findMode1(root.left);
int rootValue = root.val;
// 计数
if (pre == null || rootValue != pre.val) {
count = 1;
} else {
count++;
}
// 更新结果以及maxCount
if (count > maxCount) {
resList.clear();
resList.add(rootValue);
maxCount = count;
} else if (count == maxCount) {
resList.add(rootValue);
}
pre = root;
findMode1(root.right);
}
}
二叉树的最近公共祖先(LeetCode236)
如果找到了就返回节点,当同时找到两个节点时返回节点值
值得思考的是为什么用后序遍历,自顶向下用前序遍历,自底向上用后序,面对二叉搜索树时中序遍历得到递增的数组
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) { // 递归结束条件
return root;
}
// 后序遍历
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null && right == null) { // 若未找到节点 p 或 q
return null;
}else if(left == null && right != null) { // 若找到一个节点
return right;
}else if(left != null && right == null) { // 若找到一个节点
return left;
}else { // 若找到两个节点
return root;
}
}
}
二叉搜索树的最近公共祖先(LeetCode235)
因为是二叉搜索树所以本身就是有序的,也不用处理中间节点,值在p,q之间的节点就返回。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
}
删除二叉搜索树中的节点(LeetCode450)
将删除节点与右子树的左叶子节点交换即可
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return root;
if (root.val == key) {
if (root.left == null) {
return root.right;
} else if (root.right == null) {
return root.left;
} else {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
root = root.right;
return root;
}
}
if (root.val > key) root.left = deleteNode(root.left, key);
if (root.val < key) root.right = deleteNode(root.right, key);
return root;
}
}
修剪二叉搜索树(LeetCode669)
超出范围就递归到下一层
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) {
return null;
}
if (root.val < low) {
return trimBST(root.right, low, high);
}
if (root.val > high) {
return trimBST(root.left, low, high);
}
// root在[low,high]范围内
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
}
}
把二叉搜索树转换为累加树(LeetCode538)
写不出来
class Solution {
int sum;
public TreeNode convertBST(TreeNode root) {
sum = 0;
convertBST1(root);
return root;
}
// 按右中左顺序遍历,累加即可
public void convertBST1(TreeNode root) {
if (root == null) {
return;
}
convertBST1(root.right);
sum += root.val;
root.val = sum;
convertBST1(root.left);
}
}
回溯算法
组合(LeetCode77)
通过遍历将数字加入,递归完就将这个数字弹出。加入结果时,不能写成res.add(list);而必须写成res.add(new ArrayList<>(list))。res.add(list);是将list的地址加入,res.add(new ArrayList<>(list))表示加入一个新的对象。
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
public List<List<Integer>> combine(int n, int k) {
back(n,k,1);
return res;
}
public void back(int n,int k,int startIndex){
if(list.size()==k){
res.add(new ArrayList<>(list));
return;
}
for(int i=startIndex;i<=n;i++){
list.add(i);
back(n,k,i+1);
list.remove(list.size()-1);
}
}
}
组合总和III(LeetCode216)
和上题一样,没什么说的,再加一个条件而已
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
public List<List<Integer>> combinationSum3(int k, int n) {
back(n,k,1,0);
return res;
}
public void back(int n,int k,int startIndex,int sum){
if(list.size()==k && sum==n){
res.add(new ArrayList<>(list));
return;
}
for(int i=startIndex;i<=9;i++){
list.add(i);
sum+=i;
back(n,k,i+1,sum);
list.remove(list.size()-1);
sum-=i;
}
}
}
电话号码的字母组合(LeetCode 17)
唯一的难点就是你能不能熟练使用Java字符串相关的函数。
class Solution {
List<String> list = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backTracking(digits, numString, 0);
return list;
}
StringBuilder temp = new StringBuilder();
public void backTracking(String digits, String[] numString, int num) {
if (num == digits.length()) {
list.add(temp.toString());
return;
}
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i));
backTracking(digits, numString, num + 1);
temp.deleteCharAt(temp.length() - 1);
}
}
}
组合总和(LeetCode39)
思路没什么变化
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer> list=new ArrayList<Integer>();
back(candidates,target,list,0);
return res;
}
public void back(int[] candidates,int target,List<Integer> list,int start){
if(target==0){
res.add(new ArrayList<Integer>(list));
return;
}
for(int i=start;i<candidates.length;i++){
if(target-candidates[i]>=0){
list.add(candidates[i]);
back(candidates,target-candidates[i],list,i);
list.remove(list.size()-1);
}
}
}
}
组合总和II(LeetCode40)
唯一的区别是要去重
//去重代码
if (i > startIndex && candidates[i] == candidates[i - 1]) continue;
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
back(candidates,target,0);
return res;
}
public void back(int[] candidates,int target,int startIndex){
if(target==0){
res.add(new ArrayList<Integer>(list));
return;
}
for(int i=startIndex;i<candidates.length;i++){
if (i > startIndex && candidates[i] == candidates[i - 1]) continue;
if(target-candidates[i]>=0){
list.add(candidates[i]);
back(candidates,target-candidates[i],i+1);
list.remove(list.size()-1);
}
}
}
}
分割回文串(LeetCode 131)
可以结合电话号码那道题,再添加一个判断回文子串的函数即可
class Solution {
List<List<String>> lists = new ArrayList<>();
Deque<String> deque = new LinkedList<>();
public List<List<String>> partition(String s) {
backTracking(s, 0);
return lists;
}
private void backTracking(String s, int startIndex) {
if (startIndex >= s.length()) {
lists.add(new ArrayList(deque));
return;
}
for (int i = startIndex; i < s.length(); i++) {
if (isPalindrome(s, startIndex, i)) {
String str = s.substring(startIndex, i + 1);
deque.addLast(str);
} else {
continue;
}
backTracking(s, i + 1);
deque.removeLast();
}
}
private boolean isPalindrome(String s, int startIndex, int end) {
for (int i = startIndex, j = end; i < j; i++, j--) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
}
return true;
}
}
复原IP地址(LeetCode93)
class Solution {
List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
StringBuilder sb = new StringBuilder(s);
backTracking(sb, 0, 0);
return result;
}
private void backTracking(StringBuilder s, int startIndex, int dotCount){
if(dotCount == 3){
if(isValid(s, startIndex, s.length() - 1)){
result.add(s.toString());
}
return;
}
for(int i = startIndex; i < s.length(); i++){
if(isValid(s, startIndex, i)){
s.insert(i + 1, '.');
backTracking(s, i + 2, dotCount + 1);
s.deleteCharAt(i + 1);
}else{
break;
}
}
}
private boolean isValid(StringBuilder s, int start, int end){
if(start > end)
return false;
if(s.charAt(start) == '0' && start != end)
return false;
int num = 0;
for(int i = start; i <= end; i++){
int digit = s.charAt(i) - '0';
num = num * 10 + digit;
if(num > 255)
return false;
}
return true;
}
}
子集(LeetCode78)
子集问题只需要判断队列长度,这里空集合也要,所以索性不写
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
public List<List<Integer>> subsets(int[] nums) {
if(nums.length==0) return res;
back(nums,0);
return res;
}
public void back(int[] nums,int startIndex){
res.add(new ArrayList(list));
for(int i=startIndex;i<nums.length;i++){
list.add(nums[i]);
back(nums,i+1);
list.remove(list.size()-1);
}
}
}
子集II(LeetCode90)
去重即可
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
if(nums.length==0) return res;
back(nums,0);
return res;
}
public void back(int[] nums,int startIndex){
res.add(new ArrayList(list));
for(int i=startIndex;i<nums.length;i++){
if(i > startIndex && nums[i] == nums[i - 1]) continue;
list.add(nums[i]);
back(nums,i+1);
list.remove(list.size()-1);
}
}
}
非递减子序列(LeetCode491)
在树同一层的元素不能使用,所以加一个HashSet来进行去重判断
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums, 0);
return result;
}
private void backTracking(int[] nums, int startIndex){
if(path.size() >= 2) result.add(new ArrayList<>(path));
HashSet<Integer> hs = new HashSet<>();
for(int i = startIndex; i < nums.length; i++){
if(!path.isEmpty() && path.get(path.size() -1 ) > nums[i] || hs.contains(nums[i]))
continue;
hs.add(nums[i]);
path.add(nums[i]);
backTracking(nums, i + 1);
path.remove(path.size() - 1);
}
}
}
全排列(LeetCode46)
先确定一个,再将其他的进行排序。
//排序代码
if (used[i]) continue;
used[i] = true;
//回溯
used[i] = false;
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0){
return result;
}
used = new boolean[nums.length];
permuteHelper(nums);
return result;
}
private void permuteHelper(int[] nums){
if (path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++){
if (used[i]){
continue;
}
used[i] = true;
path.add(nums[i]);
permuteHelper(nums);
path.removeLast();
used[i] = false;
}
}
}
全排序II(LeetCode47)
去重即可
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
Arrays.sort(nums);
backTrack(nums, used);
return result;
}
private void backTrack(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
if (used[i] == false) {
used[i] = true;
path.add(nums[i]);
backTrack(nums, used);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
}
贪心算法
分发饼干(LeetCode455)
局部最优解:一块饼干尽量喂给胃口大的
class Solution {
public int findContentChildren(int[] g, int[] s) {
int i=g.length-1,j=s.length-1;
Arrays.sort(g);Arrays.sort(s);
int num=0;
while(i>=0&&j>=0){
if(s[j]>=g[i]){
num++;
i--;j--;
}
else{
i--;
}
}
return num;
}
}
精简之后:
class Solution {
public int findContentChildren(int[] g, int[] s) {
int i=g.length-1,j=s.length-1;
Arrays.sort(g);Arrays.sort(s);
int num=0;
while(i>=0&&j>=0){
if(s[j]>=g[i]){
num++;
j--;
}
i--;
}
return num;
}
}
摆动序列(LeetCode376)
局部最优:删除单调坡度上的节点;整体最优:整个序列有最多的局部峰值即最长摆动序列
curDiff:当前落差;preDiff:前一个节点落差。如果满足条件就更新
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
int curDiff = 0;
int preDiff = 0;
int count = 1;
for (int i = 1; i < nums.length; i++) {
curDiff = nums[i] - nums[i - 1];
//等于0的情况表示初始时的preDiff
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
count++;
preDiff = curDiff;
}
}
return count;
}
}
最大子数组和(LeetCode53)
一看就会,一做就废。。。
局部最优解:区间和大于0就继续,否则就跳过。全局最优解:多个大于0的局部最优解的加和
class Solution {
public int maxSubArray(int[] nums) {
int ans = Integer.MIN_VALUE;
int[] dp = new int[nums.length];
dp[0] = nums[0];
ans = dp[0];
for (int i = 1; i < nums.length; i++){
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
ans = Math.max(dp[i], ans);
}
return ans;
}
}
买卖股票的最佳时机II(LeetCode122)
把许多天拆分成相邻的两天,局部最优:收集每天的正利润,全局最优:求得最大利润。
class Solution {
public int maxProfit(int[] prices) {
int result = 0;
for (int i = 1; i < prices.length; i++) {
result += Math.max(prices[i] - prices[i - 1], 0);
}
return result;
}
}
跳跃游戏(LeetCode55)
不要纠结应该跳几步,找出能跳的范围,观察这个范围能不能包含终点
局部最优解:当前能覆盖的最大区间;全局最优解:能不能覆盖到终点
class Solution {
public boolean canJump(int[] nums) {
int coverRange=0;
for(int i=0;i<=coverRange;i++){
coverRange=Math.max(coverRange,nums[i]+i);
if(coverRange>=nums.length-1) return true;
}
return false;
}
}
跳远游戏II(LeetCode45)
局部最优解:当前能覆盖的最大区间;全局最优解:最少用多少个区间表示全局
class Solution {
public int jump(int[] nums) {
if (nums==null||nums.length<=1) return 0;
int count=0,curDistance=0,maxDistance=0;
for(int i=0;i<nums.length;i++){
maxDistance=Math.max(i+nums[i],maxDistance);
if (maxDistance>=nums.length-1){
count++;
break;
}
if (i==curDistance){
curDistance = maxDistance;
count++;
}
}
return count;
}
}
K次取反后最大化的数组和(LeetCode1005)
局部最优解:先将负数转变为正数;全局最优解:把负数变为正数后减去最小的数
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
int sum = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0 && k > 0) {
nums[i] = -1 * nums[i];
k--;
}
sum += nums[i];
}
Arrays.sort(nums);
return sum - (k % 2 == 0 ? 0 : 2 * nums[0]);
}
}
加油站(LeetCode134)
个人感觉这道题和贪心没关系,如果sum>0,而这个节点的左边是<0的,那么右边就应该>0。正确的答案就应该在右边找。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int totalSum = 0;
int index = 0;
for (int i = 0; i < gas.length; i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) {
index = (i + 1) % gas.length ;
curSum = 0;
}
}
if (totalSum < 0) return -1;
return index;
}
}
动态规划
当前状态由之前状态演变 记住这句话,好好体会
dp五步走:
1.确定dp数组以及下标的含义
2.确定递推公式
3.dp数组如何初始化
4.确定遍历顺序
5.举例推导dp数组
斐波那契数(LeetCode509)
class Solution {
public int fib(int n) {
int[] dp=new int[n+2];
dp[0]=0;dp[1]=1;
for(int i=2;i<=n;++i){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
爬楼梯(LeetCode70)
class Solution {
public int fib(int n) {
int[] dp=new int[n+2];
dp[0]=0;dp[1]=1;
for(int i=2;i<=n;++i){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
使用最小的花费爬楼梯(LeetCode746)
比较最后两个的值,因为最后两个空都可以跳出去
class Solution {
public int minCostClimbingStairs(int[] cost) {
int n=cost.length;
int[] dp =new int[n+2];
dp[0]=cost[0];dp[1]=cost[1];
for(int i=2;i<n;++i){
dp[i]=Math.min(dp[i-2],dp[i-1])+cost[i];
}
return Math.min(dp[n-2],dp[n-1]);
}
}
不同路径(LeetCode62)
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp=new int[m+2][n+2];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[1][j]=1;
dp[i][1]=1;
if(i!=1&&j!=1) dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
}
不同路径II(LeetCode63)
初始化的时候要注意,遇到障碍后面就不能初始化了
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) return 0;
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
dp[0][j] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = (obstacleGrid[i][j] == 0) ? dp[i - 1][j] + dp[i][j - 1] : 0;
}
}
return dp[m - 1][n - 1];
}
}
整数拆分(LeetCode343)
当前的最大乘积=之前两个数字的最大乘积相乘
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];
dp[2] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i-j; j++) {
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
}
}
return dp[n];
}
}
01背包
结果逼近期望值的最佳组合 记住这句话,好好体会
代码
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
}
分割等和子集(LeetCode416)
class Solution {
public boolean canPartition(int[] nums) {
int sum=0;
for(int num:nums){
sum+=num;
}
if(sum%2!=0) return false;
sum/=2;
int[] dp=new int[sum+1];
for(int i=0;i<nums.length;i++){
for(int j=sum;j>=nums[i];j--){
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
if(dp[sum]==sum) return true;
}
return dp[sum]==sum;
}
}
最后一块石头的重量||(LeetCode1049)
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum=0;
for(int num:stones){
sum+=num;
}
int target=sum>>1;
int[] dp=new int[target+2];
for(int i=0;i<stones.length;i++){
for(int j=target;j>=stones[i];j--){
dp[j]=Math.max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
return sum-2*dp[target];
}
}
目标和(LeetCode494)
把递归树画出来,就知道这道题用回溯可以解决。时间复杂度太高,不推荐
//回溯法,不推荐
class Solution {
int count=0;
public int findTargetSumWays(int[] nums, int target) {
back(nums,target,0);
return count;
}
public void back(int[] nums,int target,int startIndex){
if(startIndex==nums.length&&target==0) count++;
if(startIndex<nums.length){
back(nums,target-nums[startIndex],startIndex+1);
back(nums,target+nums[startIndex],startIndex+1);
}
}
}
因为每个数只出现一次,可以把他看作是一个找和为(sum-target)/2的01背包问题
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
int diff = sum - target;
if (diff < 0 || diff % 2 != 0) {
return 0;
}
int neg = diff / 2;
int[] dp = new int[neg + 1];
dp[0] = 1;
for (int num : nums) {
for (int j = neg; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[neg];
}
}
完全背包
代码
外物品内背包(组合数):
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
外背包内物品(排列数):
for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
for (int j = 0; j < weight.length; j++){ // 遍历物品
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
零钱兑换||(LeetCode518)
递归表达式参考数梯子那题
class Solution {
public int change(int amount, int[] coins) {
int[] dp=new int[amount+2];
dp[0]=1;
for(int i=0;i<coins.length;i++){
for(int j=coins[i];j<=amount;j++){
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
}
组合总和IV(LeetCode377)
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 0; i <= target; i++) {
for (int j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
}
零钱兑换(LeetCode322)
class Solution {
public int coinChange(int[] coins, int amount) {
int max = Integer.MAX_VALUE;
int[] dp = new int[amount + 1];
Arrays.fill(dp,max);
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
if (dp[j - coins[i]] != max) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
}
}
完全平方数(LeetCode279)
和零钱问题一样,都是找满足固定值的条件
class Solution {
public int numSquares(int n) {
int[] dp=new int[n+2];
Arrays.fill(dp,n+1);
dp[0]=0;
for(int i=0;i*i<=n;i++){
for(int j=i*i;j<=n;j++){
dp[j]=Math.min(dp[j],dp[j-i*i]+1);
}
}
return dp[n];
}
}
单词拆分(LeetCode139)
两次都没写对
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
打家劫舍(LeetCode198)
判断当前抢还是不抢即可
class Solution {
public int rob(int[] nums) {
int[] dp=new int[nums.length];
if(nums.length==1) return nums[0];
dp[0]=nums[0];dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.length-1];
}
}
打家劫舍II(LeetCode213)
需要考虑成环的问题。可以将问题转化为比较nums[0]+dp[2~n-2]和dp[1~n-1]
class Solution {
public int rob(int[] nums) {
int n=nums.length;
if(n==1) return nums[0];
if(n==2) return Math.max(nums[0],nums[1]);
return Math.max(f(nums,1,n),f(nums,2,n-1)+nums[0]);
}
public int f(int[] nums,int start,int end){
int[] dp=new int[end+1];
dp[start-1]=0;
dp[start]=nums[start];
for(int i=start+1;i<end;i++){
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[end-1];
}
}
打家劫舍|||(LeetCode337)
状态机问题,用0,1表示状态。0为不偷,1为偷。
当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
class Solution {
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}
public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left);
int[] right = robInternal(root.right);
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = left[0] + right[0] + root.val;
return result;
}
}
买卖股票的最佳时机(LeetCode121)
用res记录利润,遇到比价格低的就更新最低值,边遍历边计算利润
class Solution {
public int maxProfit(int[] prices) {
int low = Integer.MAX_VALUE;
int res = 0;
for(int i = 0; i < prices.length; i++){
low = Math.min(prices[i], low);
res = Math.max(prices[i] - low, res);
}
return res;
}
}
买卖股票的最佳时机||(LeetCode122)
个人觉得这道题比较简单,只要第二天的价格比今天高就可以买入。运用了贪心的思想
class Solution {
public int maxProfit(int[] prices) {
int res=0;
for(int i=0;i<prices.length-1;i++){
if(prices[i]<prices[i+1]) res+=prices[i+1]-prices[i];
}
return res;
}
}
买卖股票的最佳时机|||(LeetCode123)
//容易理解的版本,基于买卖股票1
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int buy1 = -prices[0], sell1 = 0;
int buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < n; ++i) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
}
//更加模板化的解法,虽然看起来只是表示方式不一样
//但是这个是状态问题最标准的解法,用二维,一个表示状态一个用来dp
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[4];
// 存储两次交易的状态就行了
// dp[0]代表第一次交易的买入
dp[0] = -prices[0];
// dp[1]代表第一次交易的卖出
dp[1] = 0;
// dp[2]代表第二次交易的买入
dp[2] = -prices[0];
// dp[3]代表第二次交易的卖出
dp[3] = 0;
for(int i = 1; i <= prices.length; i++){
// 要么保持不变,要么没有就买,有了就卖
dp[0] = Math.max(dp[0], -prices[i-1]);
dp[1] = Math.max(dp[1], dp[0]+prices[i-1]);
// 这已经是第二次交易了,所以得加上前一次交易卖出去的收获
dp[2] = Math.max(dp[2], dp[1]-prices[i-1]);
dp[3] = Math.max(dp[3], dp[2]+ prices[i-1]);
}
return dp[3];
}
}
买卖股票的最佳时机IV