【动规】1、零钱兑换
分析:用回溯用的不好的话很容易超时,最好是用动态规划。
class Solution {
public int coinChange(int[] coins, int amount) {
// 自底向上的动态规划
if(coins.length == 0){
return -1;
}
// memo[n]的值: 表示的凑成总金额为n所需的最少的硬币个数
int[] memo = new int[amount+1];
memo[0] = 0;
for(int i = 1; i <= amount;i++){
int min = Integer.MAX_VALUE;
for(int j = 0;j < coins.length;j++){
//兑换的条件:加上这枚硬币的值后amount>=0,这枚
if(i - coins[j] >= 0 && memo[i-coins[j]] < min){
min = memo[i-coins[j]] + 1;//拿上该硬币时的最小硬币数
}
}
// memo[i] = (min == Integer.MAX_VALUE ? Integer.MAX_VALUE : min);
memo[i] = min;
}
return memo[amount] == Integer.MAX_VALUE ? -1 : memo[amount];
}
}
【栈】2、有效的括号
class Solution{
public boolean isValid(String s){
LinkedList<Character> stack = new LinkedList<>();
for(char c:s.toCharArray()){
if(c =='[') stack.push(']');
else if(c == '(') stack.push(')');
else if(c == '{') stack.push('}');
else if(stack.isEmpty() || c!= stack.pop()) return false;
}
return stack.isEmpty();
}
}
括号的生成
【排序】3、排序链表
给定头结点,按升序排列并返回排序后的链表。
方法1:优先队列;
方法2:放入数组,排序后重建链表;
方法3:归并排序
方法4:快排
class Solution {
public ListNode sortList(ListNode head) {
if(head == null || head.next == null){return head;}
PriorityQueue<ListNode> q = new PriorityQueue<>(new Comparator<ListNode>(){
public int compare(ListNode o1,ListNode o2){
return o1.val-o2.val;
}
});
ListNode cur = head;
while(cur!=null){
q.add(cur);
cur = cur.next;
}
//System.out.println(q.size());
ListNode newHead = new ListNode(0);
ListNode curr = newHead;
while(!q.isEmpty()){
ListNode node = q.poll();
node.next = null;
curr.next = node;
curr = node;
}
return newHead.next;
}
}
【回溯】4、组合总和
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
if(candidates.length <= 0){return res;}
Arrays.sort(candidates);
dfs(candidates,target,new ArrayList<Integer>(),res,0);
return res;
}
private void dfs(int[] candidates,int target,ArrayList<Integer> tmp,List<List<Integer>> res,int start){
//递归出口
if(target == 0){
//判重
Collections.sort(tmp);
if(!res.contains(tmp)){
res.add(new ArrayList<>(tmp));
}
return;
}
if(target < 0){return;}
for(int i = start;i < candidates.length;i++){
//剪枝
tmp.add(candidates[i]);
dfs(candidates,target-candidates[i],tmp,res,i);
tmp.remove(tmp.size()-1);
}
}
}
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
if(target < candidates[0]){return res;}
dfs(candidates,target,new ArrayList<Integer>(),res,0);
return res;
}
private void dfs(int[] candidates,int target,ArrayList<Integer> tmp,List<List<Integer>> res,int start){
//递归结束条件
if(target == 0){
res.add(new ArrayList<>(tmp));
return;
}
if(target < 0){return;}
for(int i = start;i < candidates.length;i++){
if(target > 0){
tmp.add(candidates[i]);
dfs(candidates,target-candidates[i],tmp,res,i);
tmp.remove(tmp.size()-1);
}else{//判重
break;
}
}
}
}
【动规】5、打家劫舍
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){return 0;}
if(nums.length == 1){return nums[1];}
if(nums.length == 2){return Math.max(nums[0],nums[1]);}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i < nums.length;i++){
dp[i] = Math.max(nums[i]+dp[i-2],dp[i-1]);
}
return dp[nums.length-1];
}
}
【非数字相加】6、字符串相加
class Solution {
public String addStrings(String num1, String num2) {
int c = 0;
StringBuilder res = new StringBuilder();
int i = num1.length()-1,j = num2.length()-1;
while(i >= 0 || j >= 0 || c!=0){
int n1 = i >=0? num1.charAt(i) - '0' : 0;
int n2 = j >=0? num2.charAt(j) - '0' : 0;
res.append((n1+n2+c)%10);
c = (n1+n2+c)/10;
i--;
j--;
}
return res.reverse().toString();
}
}
链表相加
【二分查找】7、寻找峰值
class Solution {
public int findPeakElement(int[] nums) {
int left = 0,right = nums.length-1;
while(left < right){
int mid = (left+right)/2;
if(nums[mid] > nums[mid+1]){
right = mid;
}else{
left = mid+1;
}
}
return left;
}
}
【双指针】8、合并两个有序数组
从两个数组末尾开始比较,谁大谁就放在最后
这道题做了很多次,贴一下之前的代码和这次做的升级代码
//升级版
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int k = nums1.length-1;
int i = m-1,j = n-1;
while(k >=0){
int num1 = i>=0?nums1[i]:Integer.MIN_VALUE;
int num2 = j>=0?nums2[j]:Integer.MIN_VALUE;
nums1[k] = Math.max(num1,num2);
if(num1 > num2){i--;}
else{j--;}
k--;
}
}
}
//之前的代码
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
if(n ==0){return;}
if(m == 0){
for(int i = 0;i < n;i++){
nums1[i] = nums2[i];
}
return;
}
//从后边开始比较,谁大谁就到最后去
int k = nums1.length-1;
int i = m-1,j = n-1;
while(i >= 0 && j >=0 && k >=0){
if(nums1[i] >= nums2[j]){
nums1[k--] = nums1[i--];
}else{
nums1[k--] = nums2[j--];
}
}
while(j>=0){
nums1[k--] = nums2[j--];
}
}
}
【栈】9、反转每对括号间的子串
class Solution {
public String reverseParentheses(String s) {
StringBuilder sb = new StringBuilder();
char[] arr = s.toCharArray();
Stack<Integer> stack = new Stack<>();
for(int i = 0;i < arr.length;i++){
if(arr[i] == '(')
stack.push(i);
if(arr[i] == ')'){
reverse(arr,stack.pop()+1,i-1);
// for(char ch:arr){
// System.out.print(ch);
// }
// System.out.println();
}
}
for(int i = 0;i < arr.length;i++){
if(arr[i] !=')' && arr[i] !='(')
sb.append(arr[i]);
}
return sb.toString();
}
public void reverse(char[] arr,int left,int right){
while(right > left){
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
right--;
left++;
}
}
}
【dfs】10、单词搜索
感觉和岛屿数量那个题目有点像
class Solution {
private boolean find;
public boolean exist(char[][] board, String word) {
if(board == null) return false;
int m = board.length,n = board[0].length;
boolean[][] visited = new boolean[m][n];
find = false;
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
backtracking(i,j,board,word,visited,0);
}
}
return find;
}
public void backtracking(int i,int j,char[][] board,String word,boolean[][] visited,int pos){
//超出边界、已经访问过、已找到目标单词、棋盘格中当前字符已经和目标字符不一致了
if(i < 0|| i>=board.length || j<0 || j>=board[0].length || visited[i][j] || find || board[i][j]!=word.charAt(pos)){return;}
if(pos == word.length()-1){
find = true;
return;
}
visited[i][j] = true;
backtracking(i+1,j,board,word,visited,pos+1);
backtracking(i-1,j,board,word,visited,pos+1);
backtracking(i,j+1,board,word,visited,pos+1);
backtracking(i,j-1,board,word,visited,pos+1);
visited[i][j] = false;
}
}
【位运算】11、汉明距离
class Solution {
public int hammingDistance(int x, int y) {
int count = 0;
while(x!=0 || y!=0){
if((x&1) != (y&1)){
count++;
}
x>>=1;
y>>=1;
}
while(x!=0){
x>>=1;
count++;
}
while(y!=0){
y>>=1;
count++;
}
return count;
}
}
【智力题】12、每日温度
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] res = new int[temperatures.length];
res[temperatures.length-1] = 0;
for(int i = temperatures.length-2;i >= 0;i--){
int index = i+1;
while(res[index]!=0 && temperatures[i] >= temperatures[index]){
if(res[index]!=0){
index += res[index];
}else{
res[i] = index-i;
}
}
if(temperatures[i] >= temperatures[index]){
res[i] = 0;
}else{
res[i] = index-i;
}
}
return res;
}
}
/**
* 根据题意,从最后一天推到第一天,这样会简单很多。因为最后一天显然不会再有升高的可能,结果直接为0。
* 再看倒数第二天的温度,如果比倒数第一天低,那么答案显然为1,如果比倒数第一天高,又因为倒数第一天
* 对应的结果为0,即表示之后不会再升高,所以倒数第二天的结果也应该为0。
* 自此我们容易观察出规律,要求出第i天对应的结果,只需要知道第i+1天对应的结果就可以:
* - 若T[i] < T[i+1],那么res[i]=1;
* - 若T[i] > T[i+1]
* - res[i+1]=0,那么res[i]=0;
* - res[i+1]!=0,那就比较T[i]和T[i+1+res[i+1]](即将第i天的温度与比第i+1天大的那天的温度进行比较)
*/
public int[] dailyTemperatures(int[] T) {
int[] res = new int[T.length];
res[T.length - 1] = 0;
for (int i = T.length - 2; i >= 0; i--) {
for (int j = i + 1; j < T.length; j += res[j]) {
if (T[i] < T[j]) {
res[i] = j - i;
break;
} else if (res[j] == 0) {
res[i] = 0;
break;
}
}
}
return res;
}
【动规】13、买卖股票的最佳时机(购入抛售一次)
class Solution {
public int maxProfit(int[] prices) {
if(prices.length <= 1)
return 0;
int min = prices[0], max = 0;
for(int i = 1; i < prices.length; i++) {
max = Math.max(max, prices[i] - min);
min = Math.min(min, prices[i]);
}
return max;
}
}
买卖股票的最佳时机(可购入、抛售多次)
只要今天比昨天大,就抛出!
例如:1 2 3,最佳是第一天购入,第三天抛出(3-1),但是第二天抛出,第二天再买入,然后第三天抛出——(2-1)+(3-2)
两种情况的收益是一样的。
再比如:1 7 3,直接就是第一天买入,第三天抛出
class Solution {
public int maxProfit(int[] prices) {
if(prices.length == 0){return 0;}
int ans = 0;
for(int i = 1;i < prices.length;i++){
if(prices[i] > prices[i-1]){
ans+=prices[i] - prices[i-1];
}
}
return ans;
}
}
买卖股票的最佳时机(含手续费)
买卖股票的最佳时机(最多两笔交易)
14、旋转图像
矩阵顺时针旋转90°
方法一:先转置,再镜像对称。转置就是i和j互换,镜像就是找一个对称轴,前后翻转
class Solution {
public void rotate(int[][] matrix) {
//转置
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < i;j++){
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}
//镜像
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < matrix.length/2;j++){
int tmp = matrix[i][matrix.length-j-1];
matrix[i][matrix.length-j-1] = matrix[i][j];
matrix[i][j] = tmp;
}
}
}
}
15、用用 Rand7() 实现 Rand10()
/**
* The rand7() API is already defined in the parent class SolBase.
* public int rand7();
* @return a random integer in the range 1 to 7
*/
class Solution extends SolBase {
public int rand10() {
int a = rand7();
int b = rand7();
if(a > 4 && b < 4){
return rand10();
}else{
return (a+b)%10+1;
}
}
}
16、两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if(l1 == null){return l2;}
if(l2 == null){return l1;}
int c = 0;
ListNode head = new ListNode(0);
ListNode cur = head;
while(l1!=null || l2!=null || c!=0){
int num1 = l1==null?0:l1.val;
int num2 = l2==null?0:l2.val;
//切记:先取余填入节点,再计算c!!
ListNode node = new ListNode((num1+num2+c)%10);
cur.next = node;
cur = node;
c = (num1+num2+c)/10;
if(l1!=null){l1 = l1.next;}
if(l2!=null){l2 = l2.next;}
}
return head.next;
}
}
17、爬楼梯
class Solution {
public int climbStairs(int n) {
if(n == 1){return 1;}
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i=2;i<n+1;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
}
18、删除链表的倒数第N个节点
19、二叉树展开为链表
方法一:倒序的先序遍历(右左中)。聪明人用递归是真没错。
方法二:先序遍历+标记,遍历过的节点标记了。
class Solution {
TreeNode last= null;
public void flatten(TreeNode root) {
if(root == null)return;
flatten(root.right);
flatten(root.left);
root.right = last;
root.left = null;
last = root;
}
}
//虽然很慢,但是很好理解
class Solution {
List<TreeNode> list = new ArrayList<>();
public void flatten(TreeNode root) {
if(root == null){return;}
preOrder(root);
//System.out.println(list.size());
root = list.get(0);
for(int i = 1;i < list.size();i++){
root.left = null;
root.right = list.get(i);
root = root.right;
}
root.right = null;
}
public void preOrder(TreeNode root){
if(root == null){return;}
if(!list.contains(root)){
list.add(root);
}
preOrder(root.left);
preOrder(root.right);
}
}
20、搜索二维矩阵II
从右上角开始搜索可以避免判断边界
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i = 0,j = matrix[0].length-1;
while(i < matrix.length && j >=0){
if(matrix[i][j] > target){
j--;
}else if(matrix[i][j] < target){
i++;
}else{
return true;
}
}
return false;
}
}
21、删除排序链表中的重复元素II
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null){return head;}
ListNode next = head.next;
if(head.val == next.val){
while(next!=null && head.val == next.val){
next = next.next;
}
//这里的意思是,暂且将next作为head,然后递归判断,假如到了null还是重复的,那么null就设置为head
head = deleteDuplicates(next);
}else{
//这里head因为没有重复,所以能够纳入
head.next = deleteDuplicates(next);
}
return head;
}
}
22、二叉搜索树中第K小的元素
中序遍历,然后到K停止。
一次AC,奈斯
class Solution {
int count = 0;
int res = 0;
public int kthSmallest(TreeNode root, int k) {
inOrder(root,k);
return res;
}
private void inOrder(TreeNode root,int k){
if(root == null){
return;
}
inOrder(root.left,k);
count++;
if(count == k){
res = root.val;
return;
}
inOrder(root.right,k);
}
}
23、平衡二叉树
定义:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
递归判断每个节点的左右子树高度,假如绝对值>1,那么返回false
今天状态真不戳,连续好几题都是一次AC
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null){return true;}
if(Math.abs(depth(root.left)-depth(root.right)) > 1){
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
private int depth(TreeNode root){
if(root == null){return 0;}
int left = depth(root.left);
int right = depth(root.right);
return Math.max(left,right)+1;
}
}
24、最小路径和
动态规划
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
//把左边界填满
for(int i = 1;i < m;i++){
dp[i][0] = dp[i-1][0]+grid[i][0];
}
//把上边界填满
for(int j = 1;j < n;j++){
dp[0][j] = dp[0][j-1]+grid[0][j];
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
}
25、二叉树的最大宽度
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int maxW = 0;
public int widthOfBinaryTree(TreeNode root) {
dfs(root, 1, 1, new ArrayList<>());
return maxW;
}
//left记录每一层的最左边结点的index,递归每个结点,用他们的index减去每一层最左边结点的index
private void dfs(TreeNode r,int level,int index,List<Integer> left){
if(r == null) return;
System.out.println(level + " "+left.size()+" "+index+" "+maxW);
if(level > left.size()) left.add(index);
maxW = Math.max(maxW,index-left.get(level-1)+1);
dfs(r.left,level+1,index*2,left);
dfs(r.right,level+1,index*2+1,left);
System.out.println("递归后"+level + " "+left.size()+" "+index+" "+maxW);
}
}
26、盛最多水的容器
接雨水
class Solution {
public int maxArea(int[] a) {
if(a.length <=1){return 0;}
int max = 0;
for(int i = 0,j = a.length-1;i<j;){
int minHeight = a[i] < a[j]?a[i++]:a[j--];
max = Math.max(max,(j-i+1)*minHeight);
}
return max;
}
}
27、下一个更大元素III
public class Solution {
public int nextGreaterElement(int n) {
char[] a = ("" + n).toCharArray();
int i = a.length - 2;
while (i >= 0 && a[i + 1] <= a[i]) {
i--;
}
if (i < 0)
return -1;
int j = a.length - 1;
while (j >= 0 && a[j] <= a[i]) {
j--;
}
swap(a, i, j);
reverse(a, i + 1);
try {
return Integer.parseInt(new String(a));
} catch (Exception e) {
return -1;
}
}
private void reverse(char[] a, int start) {
int i = start, j = a.length - 1;
while (i < j) {
swap(a, i, j);
i++;
j--;
}
}
private void swap(char[] a, int i, int j) {
char temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}