leecode刷题

数组:1两数之和,53最大字序和,945使数组唯一的最小增量,1014最佳观光组合,15三数之和,768最多能完成排序的块||

1两数之和

题目描述:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

解题代码:

import java.util.*;
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //判断Map中是否存target - Vcurrent
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            if(map.containsKey(target - nums[i])){
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i],i);
        }
       // throw new RunTimeException("未得到结果");
        throw new IllegalArgumentException("No two sum solution");
    }
}

思路:

1.利用HashMap,key和value分别为数值和数值在数组中的位置

2.判断target - currentvalue的差是否在map中;

(1)如果在map没有,则将currentvalue和currentvalue的位置储存在数组中

(2)如果map中有,则直接返回差的位置和currentvalue的位置

53最大子序和

题目描述:

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解题代码

class Solution {
    public int maxSubArray(int[] nums) {
        //使用数组的第一个数初始化res
        int res = nums[0];
        int sum = 0;
        for(int i: nums){
            sum = (sum >= 0) ? sum+i : i;//如果前面的和小于0,则sum抛弃
            res = (sum > res) ?  sum : res;//res的结果为res和sum中较大的数,只有sum的结果大于res的时候才会更新
        }
        return res;
    }
}

解题思路

  1. 使用动态规划:

     (1)sum会抛弃小于0的序列;
     
     (2)res的结果为res和sum中较大的数,只有sum的结果大于res的时候才会更新
    
  2. 使用数组的第一个数初始化res

945 使数组唯一的最小增量

题目描述:

给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1。

返回使 A 中的每个值都是唯一的最少操作次数。

示例 1:

输入:[1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。

示例 2:

输入:[3,2,1,2,1,7]
输出:6
解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。

解题代码

class Solution {
    public int minIncrementForUnique(int[] A) {
        //思路:1、先排序;2、从头遍历,若后面的数 <= 前面的数,则后面的数 = 前面的数+1

        Arrays.sort(A);
        int move = 0;
        for(int i = 1; i < A.length; i++){
            if(A[i] <= A[i-1]){
                int tem = A[i];
                A[i] = A[i-1] + 1;
                move += A[i] - tem;
            }
        }
        return move;
    }
}

解题思路:

1、先排序:Arrays.sort(A);

2、从头遍历,若后面的数 <= 前面的数,则后面的数 = 前面的数+1

有更好的办法:
线性探测法 O(N)O(N) (含路径压缩)

1014 最佳观光组合

题目描述

给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。

一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。

返回一对观光景点能取得的最高分。

示例:

输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

解题代码

class Solution {
    public int maxScoreSightseeingPair(int[] A) {
        //翻译:题目意思为:数组中求两个元素之和减去位置之差的最大值的一对元素A[i] + A[j] + i - j

        int res = 0;
        int max = A[0] + 0;
        
        for(int i = 1; i < A.length; i++){
            res = Math.max(res, A[i] - i + max);
            max = Math.max(max, A[i] + i );
        }
        return res;
    }
}

解题思路

(1)变换公式:A[i] + A[j] + i - j = (A[i] + i)+ (A[j] - j ),这样对于统计景点j的答案的时候,由于 A[j] − j 是固定不变的,因此最大化 A[i]+i+A[j]−j 的值其实就等价于求 [0,j-1]中 A[i]+i 的最大值

(2)动态更新max的值

15. 三数之和

题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

解题代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //对数组进行爱旭
        Arrays.sort(nums);
        int len = nums.length;
        List<List<Integer>> lists = new ArrayList<>();
        for(int i = 0; i < len-2; i++){
            //由于数组是升序,当遍历到大于0的数,与后面的数相加不可能再等于0了
            if(nums[i] >0 ){
                return lists;
            }
            //跳过遍历过程中的重复元素
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            //双指针
            int L = i + 1;
            int R = len - 1;
            while(L < R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]); 
                    list.add(nums[L]); 
                    list.add(nums[R]); 
                    lists.add(list);
                    //跳过左指针的重复元素
                    while(L < R && nums[L] == nums[L + 1]){
                        L++;
                    }
                    //跳过右指针的重复元素
                    while(L < R && nums[R] == nums[R - 1]){
                        R--;
                    }
                    //挪动左右指针。左右指针的数一个变大,一个变小,才有可能重新和为0,因此两个指针要同时移动
                    L++;
                    R--;
                    //左指针越向右越大,右指针越向左越小。因此当和大于0的时候要挪动右指针,和小于0的时候要挪动左指针
                }else if(sum > 0){
                    R--;
                }else{
                    L++;
                }
            }
        }
        return lists;
    }
}

解题思路

1、对元素进行从小到大的排序
2、在遍历元素的过程中利用双指针

(1)**左指针越向右越大,右指针越向左越小**。因此当和大于0的时候要挪动右指针,和小于0的时候要挪动左指针
(2)当和为0以后,再次挪动左右指针。左右指针的数一个变大,一个变小,才有可能重新和为0,因此两个指针要同时移动
(3)由于数组是升序,当遍历到大于0的数,与后面的数相加不可能再等于0了

3、去重

(1)遍历元素的去重
(2)左指针去重
(3)右指针去重

769. 最多能完成排序的块

题目描述

数组arr是[0, 1, …, arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。

我们最多能将数组分成多少块?

示例 1:

输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

示例 2:

输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

解题代码

class Solution {
    public int maxChunksToSorted(int[] arr) {
        int res = 0;
        int max = 0;
        //如果前i个元素的最大值max > i,说明在i的右侧一定有小于于i的值,因此在块排序后不能满足升序
        //如果前i个元素的最大值max == i,说明i位置及左侧的所有元素都小于等于i,i右侧的元素都大于i,可以作为块进行排序
        for(int i = 0; i < arr.length; i++){
            max = Math.max(max, arr[i]);
            if(max == i){
                res++;
            }
        }
        return res;
    }
}

思路

    //如果前i个元素的最大值max < i,说明在i的右侧一定有大于i的值,因此在块排序后不能满足升序
    
    //如果前i个元素的最大值max == i,说明i位置及左侧的所有元素都小于等于i,i右侧的元素都大于i,可以作为块进行排序

字符串
在这里插入图片描述
5. 最长回文子串

题目描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:

输入: “cbbd”
输出: “bb”

解题代码

class Solution {
    //暴力破解,遍历数组一遍,判断回文

    //使用resLen记录当前回文的长度,
       public String longestPalindrome(String s) {
           int len = s.length();
           if(len < 2){
               return s;
           }

           char[] strArray = s.toCharArray();
            int start = 0;
            //最小的回文长度就是1
            int resLen = 1;
           for(int i = 0; i < len; i++){
               for(int j = i + resLen; j < len; j++){
                   //只有当前子串为回文并且长度大于resLen的时候,才更新回文的起始位置和长度
                   if( j + 1 - i > resLen && judgeStr(strArray, i, j)){
                       start = i;
                       resLen = j + 1 - i; 
                   }
               }
           }

           return s.substring(start, start + resLen);

       }
        //判断是否是回文
       public boolean judgeStr(char[] strArray, int left, int right){
           while(left < right){
               if(strArray[left] != strArray[right]){
                   return false;
               }
               left++;
               right--;
           }
           return true;
       } 

}


思路

1、暴力破解(与双指针类似),遍历数组一遍,判断回文

2、只有当前子串为回文并且长度大于之前的回文长度resLen的时候,才更新回文的起始位置和长度
3、移动起始位置后,直接从start+resLen的位置判断是否是回文
4、单独写根据数组和起始位置判断回文的方法

3. 无重复字符的最长子串

题目描述

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

示例 4:

输入: s = “”
输出: 0

解题代码

class Solution {

    public int lengthOfLongestSubstring(String s) {
       
        if(s.length() < 2){
            return s.length();
        }

        Map<Character,Integer> map = new HashMap<Character,Integer>();
        char[] charArray = s.toCharArray();
        int start = 0;
        int max = 0;
        for(int i = 0; i < s.length(); i++){

            if(map.containsKey(charArray[i])){
                //Math.max()的作用是:abba这种情况,扫描到第二个a的时候,此时start指向的是第二个b,map.get(a) + 1得到的是第一个b,但我们需要的是从第二个b开始,因此需要Math.max(start,map.get(a) + 1)
                start = Math.max(start,map.get(charArray[i]) + 1);
            }
            max = Math.max(max, i+1-start);
            map.put(charArray[i],i);
        }
        
        return max;
    }
}

解题思路:

滑动窗口
1、一个指针指向窗口的起始位置start,记录窗口的最大长度len

2、在遍历数组的过程中:

1.用map储存数组的元素和位置
2.如果当前元素在map中存在,则更新start
3.每遍历一个元素,都窗口最大长度

415. 字符串相加

题目描述

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

提示:

num1 和num2 的长度都小于 5100
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

解题代码

class Solution {
    public String addStrings(String num1, String num2) {
        //模拟人工算法,从尾部开始加
        //1.两数相加
        //2.计算进位
        //3.添加当前位
        //4.位数溢出处理
        StringBuilder sb = new StringBuilder("");
        int len1 = num1.length() - 1;
        int len2 = num2.length() - 1;
        int carry = 0;
        while(len1 >= 0 || len2 >= 0){
            int n1 = len1 >= 0 ? num1.charAt(len1) - '0': 0;
            int n2 = len2 >= 0 ? num2.charAt(len2) - '0': 0;

            int tem = n1 + n2 + carry;
            
            carry = tem/10;
            int curr = tem%10;

            sb.append(curr);

            len1--;
            len2--;
        }

        if(carry > 0){
            sb.append(carry);
        }
        
        return sb.reverse().toString();
    }
}

思路

模拟人工算法,从尾部开始加

    //1.两数相加
    //2.计算进位
    //3.添加当前位
    //4.位数溢出处理,若一个字符串位数溢出了,则在前面加0,保证可以正常相加

93. 复原IP地址

题目描述

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。

示例 1:

输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]

示例 2:

输入:s = “0000”
输出:[“0.0.0.0”]

示例 3:

输入:s = “1111”
输出:[“1.1.1.1”]

示例 4:

输入:s = “010010”
输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:

输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

解题代码

class Solution {
    public List<String> restoreIpAddresses(String s) {
        //将字符串分为[0,a),[a,a+b),[a+b,a+b+c),[a+b+c,a+b+c+d)四个区间
        List<String> list = new ArrayList<>();
        StringBuilder sb = new StringBuilder("");
        for(int a = 1; a < 4; a++){
            for(int b = 1; b < 4; b++){
                for(int c = 1; c < 4; c++){
                    for(int d = 1; d < 4; d++){
                        //如果a+b+c+d == 字符串长度,就可以分割字符串了
                        if(a+b+c+d == s.length()){
                            int seg1 = Integer.parseInt(s.substring(0,a));
                            int seg2 = Integer.parseInt(s.substring(a,a+b));
                            int seg3 = Integer.parseInt(s.substring(a+b,a+b+c));
                            int seg4 = Integer.parseInt(s.substring(a+b+c,a+b+c+d));
                            //判断每段是否都小于等于255
                            if(seg1 <= 255 && seg2 <= 255 && seg3 <= 255 && seg4 <= 255){
                                //拼接ip地址
                                sb.append(seg1).append(".").append(seg2).append(".").append(seg3).append(".").append(seg4);
                                //排除前导0的情况
                                if(sb.length() == s.length() + 3){
                                    list.add(sb.toString());
                                }
                                sb.delete(0,sb.length());
                            }
                        }
                    }
                }
            }
        }
        return list;
    }
}

思路:

1.注意审题,ip地址的格式:

(1)四个整数
(2)每个整数位于 0 到 255 之间组成
(3)不能含有前导 0
(4)整数之间用 '.' 分隔

2.四层循环将字符串分为四个整数,相当于四个指针,将字符串分为[0,a),[a,a+b),[a+b,a+b+c),[a+b+c,a+b+c+d)四段;如果a+b+c+d == 字符串长度,就可以分割字符串了

3.判断是否含有前导零:判断加点后的字符串长度sb.length() == s.length() + 3。(如果有前导零,则将字符串转为整型的时候长度就会变短)

动态规划
在这里插入图片描述
300. 最长上升子序列

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

代码

class Solution {
    public int lengthOfLIS(int[] nums) {

        int[] arr = new int[nums.length];

        Arrays.fill(arr,1);

        for(int i = 1; i < nums.length; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    arr[i] = Math.max(arr[i],arr[j]+1);
                }
            }
        }
        
        int max = 1;
        for(int i = 0; i < nums.length; i++){
            max = Math.max(max, arr[i]);
        }

        return max;

    }
}

解题思路

  1. 定义状态:由于一个子序列一定会以一个数结尾,于是将状态定义成:dp[i] 表示 以 nums[i] 结尾 的「上升子序列」的长度。注意:这个定义中 nums[i] 必须被选取,且必须是这个子序列的最后一个元素
  2. 考虑状态转移方程:
    遍历到 nums[i] 时,需要把下标 i 之前的所有的数都看一遍;
    只要 nums[i] 严格大于在它位置之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列;因此,dp[i] 就等于下标 i 之前严格小于 nums[i] 的状态值的最大者 +1。

486预测赢家

题目描述

定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。

示例 2:

输入:[1, 5, 233, 7]
输出:True
解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 5 和 7 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。
最终,玩家 1(234 分)比玩家 2(12 分)获得更多的分数,所以返回 True,表示玩家 1 可以成为赢家。

提示:

1 <= 给定的数组长度 <= 20.
数组里所有分数都为非负数且不会大于 10000000 。
如果最终两个玩家的分数相等,那么玩家 1 仍为赢家。

代码

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        if(helper(0, nums.length-1, nums) >= 0){
            return true;
        }else{
            return false;
        }
    }

    public int helper(int left, int right, int[] nums){
        if(left == right){
            return nums[left];
        }
        int chooseLeft = nums[left] - helper(left+1, right, nums);
        int chooseRight = nums[right] - helper(left, right-1,nums);
        return Math.max(chooseLeft,chooseRight);
    }
}

1.记忆化递归

2.使用array[i][j]储存中间值
3.自底向上分析,自顶向下计算

(1)nums[left] 为选择左边的数后本次的得分,helper(left+1, right, nums)为在以后的游戏中对手的得分;
int chooseLeft = nums[left] - helper(left+1, right, nums);
(2)nums[left] 为选择右边的数后本次的得分,helper(left+1, right, nums)为在以后的游戏中对手的得分;
int chooseRight = nums[right] - helper(left, right-1,nums);

参考

https://www.bilibili.com/video/BV1h54y127xL?from=search&seid=13238785976047556464

1024视频拼接
数组排序
动态规划

题目描述

你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。

视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。

我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。

示例 1:

输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10
输出:3
解释:
我们选中 [0,2], [8,10], [1,9] 这三个片段。
然后,按下面的方案重制比赛片段:
将 [1,9] 再剪辑为 [1,2] + [2,8] + [8,9] 。
现在我们手上有 [0,2] + [2,8] + [8,10],而这些涵盖了整场比赛 [0, 10]。

示例 2:

输入:clips = [[0,1],[1,2]], T = 5
输出:-1
解释:
我们无法只用 [0,1] 和 [1,2] 覆盖 [0,5] 的整个过程。

示例 3:

输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], T = 9
输出:3
解释:
我们选取片段 [0,4], [4,7] 和 [6,9] 。

示例 4:

输入:clips = [[0,4],[2,8]], T = 5
输出:2
解释:
注意,你可能录制超过比赛结束时间的视频。

提示:

1 <= clips.length <= 100
0 <= clips[i][0] <= clips[i][1] <= 100
0 <= T <= 100

代码

class Solution {
    public int videoStitching(int[][] clips, int T) {
        int[] dp = new int[T+1];
        dp[0] = 0;
        for(int i = 1; i < dp.length; i++){
            dp[i] = Integer.MAX_VALUE-1;
        }
        for(int i = 1; i <= T; i++){
            for(int[] clip: clips){
                if(clip[0] < i && clip[1] >= i){
                    dp[i] = Math.min(dp[i], dp[clip[0]]+1);
                }
            }
        }
        return dp[T] > 100?-1:dp[T];
    }
}

解题思路

**状态:**用dp[i]表示拼接i秒的视频需要拼接的最小片段
**状态转移方程:**dp[clip[0]]是拼接clip[0]需要的最小片数,因此转移方程为:时间为i的最小时间片为遍历完时间片之后,满足完成时间为i的片段所需要的最小时间片

极小化极大问题

322. 零钱兑换

题目

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

解题代码

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(amount == 0){
            return 0;
        }
        if(amount < 0){
            return -1;
        }
        //新建状态
        int[][] f = new int[coins.length+1][amount+1]; 
        //为初始条件赋值
        Arrays.fill(f[coins.length],Integer.MAX_VALUE);
        f[coins.length][0] = 0;
        //遍历硬币数量
        for(int i = coins.length-1; i >= 0; i--){
            //遍历金额
            for(int j = 0; j <= amount; j++ ){
            //赋初始值
            f[i][j] = f[i+1][j];
            //设置k的定义域
            int maxK = j/coins[i];
            //遍历k
            for(int k = 1; k <= maxK; k++){
                int pre = f[i+1][j - k*coins[i]];
                if(pre < Integer.MAX_VALUE){
                    f[i][j] = Math.min(f[i][j], pre+k);
                }

            }
                //f[i][j] = min(f[i+1][j], f[i+1][j-k*coins[i]+k)
            }
         
        }
        
        return f[0][amount] == Integer.MAX_VALUE ? -1 : f[0][amount];
    }
}

思路

动态规划
状态

f[i][j]代表使用第i枚到第n-1枚硬币(硬币金额从大到小排序)能组成j元的最小数量;

状态转移方程:

f[i][j] = min(f[i+1][j], f[i+1][j-k*coins[i]+k),0 =< k =< j/coins[i]

初始条件

f[n][0] = 0;
f[n][1....m] = infinity

在这里插入图片描述
参考:htps://www.youtube.com/watch?v=htdBJul3xoc

718. 最长重复子数组

题目描述

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。

解题代码

class Solution {
    public int findLength(int[] A, int[] B) {
        //初始化dp
        int a = A.length;
        int b = B.length;
        int[][] dp = new int[a+1][b+1];
        int res = 0;
        //dp[i][j] = dp[i-1][j-1] + 1;
        for(int i = 1; i <= a; i++){
            for(int j = 1; j <= b; j++){
                if(A[i-1] == B[j-1]){
                    dp[i][j] =  dp[i-1][j-1] + 1;
                    res = Math.max(res, dp[i][j]);
                }
            }
        }
        return res;

    }
}

思路:

遍历A、B数组,遍历到相同的元素时,考虑它们俩前面的子数组「能为它们俩提供多大的公共长度」

	提供多长的公共长度?这又取决于前缀数组的末尾项是否相同……
(1)如果它们俩的前缀数组的「末尾项」不相同,由于子数组的连续性,前缀数组不能为它们俩提供公共长度
(2)如果它们俩的前缀数组的「末尾项」相同,则可以为它们俩提供公共长度:

状态转移方程

dp[i][j] :长度为 i,末尾项为 A[i−1] 的子数组,与,长度为j,末尾项为 B[j−1] 的子数组,二者的最大公共后缀子数组长度。
如果 A[i-1] != B[j-1], 有 dp[i][j] = 0
如果 A[i-1] == B[j-1] , 有 dp[i][j] = dp[i-1][j-1] + 1

base case:

如果i==0||j==0,其中一个子数组长度为 0,没有公共部分,dp[i][j] = 0
最长公共子数组以哪一项为末尾项都有可能,即每个 dp[i][j] 都可能是最大值。

在这里插入图片描述

873. 最长的斐波那契子序列的长度

题目描述

如果序列 X_1, X_2, …, X_n 满足下列条件,就说它是 斐波那契式 的:

n >= 3
对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}
给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

(回想一下,子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)

示例 1:

输入: [1,2,3,4,5,6,7,8]
输出: 5
解释:
最长的斐波那契式子序列为:[1,2,3,5,8] 。

示例 2:

输入: [1,3,7,11,12,14,18]
输出: 3
解释:
最长的斐波那契式子序列有:
[1,11,12],[3,11,14] 以及 [7,11,18] 。

代码

class Solution {
    public int lenLongestFibSubseq(int[] A) {
        //定义res = 2;
        int res = 2;
        //定义map,记录值和位置,初始化
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i = 0; i < A.length; i++){
            map.put(A[i],i);
        }
        //定义dp[i][j](i < j)定义为从从第i位到第j位的最长的斐波那契子序列的长度,初始化为2
        int[][] dp = new int[A.length][A.length];
        for(int[] d : dp){
            Arrays.fill(d,2);
        }
        //*********************动态规划核心代码***********************
        //j从第二位开始向后遍历
        for(int j = 2; j < A.length; j++){
             //i从第j-1为开始向前遍历
             for(int i = j-1; i >= 0; i--){
                //判断A[j]-A[i]是否在map内(并且2*A[i]>A[j],否则继续向前遍历没意义了)
                if(2*A[i]>A[j] && map.containsKey(A[j]-A[i])){
                    //如果在,则令k = map.get(A[j]-A[i])(由于2*A[i]>A[j],因此k<i),dp[i][j] = dp[k][i]+1;
                    int k = map.get(A[j]-A[i]);
                    dp[i][j] = dp[k][i]+1;
                    res = Math.max(res,dp[i][j] );
                }
                    
             }
                
        }
        return res == 2 ? 0 : res; 
          
    }
}

思路:动态规划

状态:dp[i][j]为数组中从第i位到第j位的斐波那契子数列的长度,i < j
状态转移方程:如果发现数组中存在A[j]-A[i],那么dp[i][j] = dp[(A[j]-A[i])的位置]][i]+1。

983. 最低票价

题目描述

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, …, 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, …, 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。
你总共花了 $17,并完成了你计划的每一天旅行。

代码

class Solution {
    public int mincostTickets(int[] days, int[] costs) {
        int len = days.length, maxDay = days[len - 1], minDay = days[0];
        int[] dp = new int[maxDay + 31]; // 多扩几天,省得判断 365 的限制
        // 只需看 maxDay -> minDay,此区间外都不需要出门,不会增加费用
        for (int d = maxDay, i = len - 1; d >= minDay; d--) {
            // i 表示 days 的索引
            // 也可提前将所有 days 放入 Set,再通过 set.contains() 判断
			//d代表的实际的第几天,每次for循环都是--操作;i是天数数组的下标,只有当d==days[i]的时候,才代表天数数组中有这一天。
			//否则天数数组中没这一天,当天没有出门,只需要让dp[d]==dp[d+1]if (d == days[i]) {
                dp[d] = Math.min(dp[d + 1] + costs[0], dp[d + 7] + costs[1]);
                dp[d] = Math.min(dp[d], dp[d + 30] + costs[2]);
                i--; // 别忘了递减一天
            } else dp[d] = dp[d + 1]; // 不需要出门
        }
        return dp[minDay]; // 从后向前遍历,返回最前的 minDay
    }
}

思路

从后往前推,用d[i]表示第i天的总的最小花费,假设输入是(8,30)。dp[30] = cost(0),因为第29天没有旅行,所以dp[29] = d[30],同理,d[28] = dp[29]…dp[9] = dp[10],到了第8天,有三种选择,分别是cost[0]+dp[9],cost[1]+d[8+7],cost[2]+dp[8+30],取其中的最小值。
动态规划

**状态**:dp[i]表示从第i天到最后一天的最小花费
**状态转移方程**:
dp[i] = min(决策1, 决策2, 决策3);
      = min(c[0] + 1天后不包, c[1] + 7天后不包, c[2] + 30天不包);
      = min(c[0] + dp[i + 1], c[1] + dp[i + 7], c[2] + dp[i + 30]);

二叉树
226. 翻转二叉树

题目描述

输入:
4
/
2 7
/ \ /
1 3 6 9
输出:

 4

/
7 2
/ \ /
9 6 3 1

解题代码

class Solution {
	public TreeNode invertTree(TreeNode root) {
		//递归函数的终止条件,节点为空时返回
		if(root==null) {
			return null;
		}
		//下面三句是将当前节点的左右子树交换
		TreeNode tmp = root.right;
		root.right = root.left;
		root.left = tmp;
		//递归交换当前节点的 左子树
		invertTree(root.left);
		//递归交换当前节点的 右子树
		invertTree(root.right);
		//函数返回时就表示当前这个节点,以及它的左右子树
		//都已经交换完了
		return root;
	}
}

思路:

**终止条件**:当前节点为 null 时返回
交换当前节点的左右节点,再递归的交换当前节点的左节点,递归的交换当前节点的右节点
  1. 二叉树的直径

题目描述

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :

给定二叉树

      1
     / \
    2   3
   / \     
  4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

代码

class Solution {
    int res = 0; 
    public int diameterOfBinaryTree(TreeNode root) {
        dfs(root);
        //此方法的作用是返回res
        return res;
    }

    // 函数dfs的作用是:找到以root为根节点的二叉树的最大深度
    public int dfs(TreeNode root){
        if(root == null){
            return 0;
        }
        int leftDepth = dfs(root.left);
        int rigthDepth = dfs(root.right);
        //在递归计算深度的过程中不断更新res
        res = Math.max(res,leftDepth + rigthDepth);
        return Math.max(leftDepth,rigthDepth) + 1;
    }
}

思路

在这里插入图片描述
子节点Y的最大深度就是Y上的节点到父节点X的最大距离

101. 对称二叉树

题目描述:

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1

/
2 2
/ \ /
3 4 4 3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1

/
2 2
\
3 3

进阶:

你可以运用递归和迭代两种方法解决这个问题吗?

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return isMirror(root, root);
    }

    public boolean isMirror(TreeNode root1, TreeNode root2){
        if(root1 == null && root2 == null){
            return true;
        }
        //如果其中一个为空,另一个不为空,则两个不相等
        if(root1 == null || root2 == null){
            return false;
        }
        return root1.val == root2.val && 
        isMirror(root1.left, root2.right) && 
        isMirror(root1.right, root2.left);
    }
}

思路

该问题可以转化为:两个树在什么情况下互为镜像?

如果同时满足下面的条件,两个树互为镜像:

1.它们的两个根结点具有相同的值
2.每个树的右子树都与另一个树的左子树镜像对称

606. 根据二叉树创建字符串

题目描述

你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。

空节点则用一对空括号 “()” 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

示例 1:

输入: 二叉树: [1,2,3,4]
1
/
2 3
/
4

输出: “1(2(4))(3)”

解释: 原本将是“1(2(4)())(3())”,
在你省略所有不必要的空括号对之后,
它将是“1(2(4))(3)”。
示例 2:

输入: 二叉树: [1,2,3,null,4]
1
/
2 3
\
4

输出: “1(2()(4))(3)”

解释: 和第一个示例相似,
除了我们不能省略第一个对括号来中断输入和输出之间的一对一映射关系。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public String tree2str(TreeNode t) {
        if(t == null){
            return "";
        }
        if(t.left == null && t.right == null){
            return t.val+"";
        }
        if(t.right == null){
            return t.val + "("+tree2str(t.left)+")";
        }
        return t.val + "("+tree2str(t.left)+")"+ "("+tree2str(t.right)+")";
    }
}

思路:

1.将递归结果放在括号内!
2.如果右子树的内容为空,则可以忽略不写
3.如果左子树的内容为空,则必须写上空的括号,就像正常的递归一样!

110. 平衡二叉树

题目描述

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例 1:

在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:

在这里插入图片描述

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

示例 3:

输入:root = []
输出:true

提示:

树中的节点数在范围 [0, 5000] 内
-104 <= Node.val <= 104

/**
 * 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 {
    public boolean isBalanced(TreeNode root) {
        if(root == null){
            return true;
        }
        //计算左右子树的深度
        int leftDep = treeDepth(root.left);
        int rightDep = treeDepth(root.right);
        //比较左右子树的深度
        int distance = leftDep - rightDep;
        return Math.abs(distance) <= 1 && isBalanced(root.left) && isBalanced(root.right);
        
    }
    public int treeDepth(TreeNode root){
        if(root == null){
            return 0;
        }
        int leftDep = treeDepth(root.left);
        int rightDep = treeDepth(root.right);
        return Math.max(leftDep, rightDep) + 1;
    }
}

思路:
注意:平衡二叉树要求每个节点都为平衡二叉树,及每个节点的左右子树的深度之差都不能超过1。

199. 二叉树的右视图

题目描述

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

1 <—
/
2 3 <—
\
5 4 <—

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<Integer> res = new ArrayList<Integer>();
    public List<Integer> rightSideView(TreeNode root) {
        addElement(root,0);
        return res;
    }

    public void addElement(TreeNode root, int depth){
        if(root == null){
            return;
        }
        //只有当递归深度==集合中元素数量的时候,表示当前层还没有被访问过,可以被添加到集合中
        if(depth == res.size()){
            res.add(root.val);
        }
        //用depth记录递归的深度,
        depth++;
        addElement(root.right, depth);
        addElement(root.left,depth);

    }
}
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    
    public List<Integer> rightSideView(TreeNode root) {
        
        List<Integer> res = new ArrayList<Integer>();
        if(root == null){
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(queue.size() != 0){
            //size是本层节点的个数
            int size = queue.size();
            //储存本层节点的数量 -> 按顺序弹出A -> 储存A的左右孩子 -> 访问A
            for(int i = 0; i < size; i++){
                //依次将本层节点弹出,本层节点的访问由i和size控制
                TreeNode node = queue.poll();
                //在队列中按从左到右的顺序储存下一层节点
                if(node.left != null){
                    queue.offer(node.left);
                }
                if(node.right != null){
                    queue.offer(node.right);
                }
                //当 i == size的时候,就代表访问的是本层最后一个节点
                if(i == size -1){
                    res.add(node.val);
                }
            }
        }
        return res;
    }

   
}

思路:
1.深度优先搜索

1.按照 「根结点 -> 右子树 -> 左子树」 的顺序访问,就可以保证每层都是最先访问最右边的节点的。
2.用depth记录递归的深度,只有当递归深度==集合中元素数量的时候,表示当前层还没有被访问过,可以被添加到集合中

2.广度优先搜索

1.使用队列储存各层节点
2.只要队列不为空,就可以继续访问,因此大循环为while(!queue.isEmpty())
3.当一个while循环结束的时候,代表当前层的节点一层被访问完了,开始访问下一层节点
4.在访问开始前,用size储存本层节点的个数
5.使用for循环依次访问本层节点,利用size控制只访问本层的节点:
	 按顺序弹出A -> 储存A的左右孩子 -> 访问A
6.当 i == size的时候,就代表访问的是本层最后一个节点,可以储存在结果的集合中。

536从字符串生成二叉树

题目描述

在这里插入图片描述

代码

/**
 * 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 {
    public TreeNode str2tree(String s) {
        if (s.length() == 0) return null;
        StringBuilder value = new StringBuilder();
        TreeNode root = new TreeNode();
        int start = -1; // 第一个左括号的位置
        // 确定根节点的值
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                root.val = Integer.parseInt(value.toString());
                start = i;
                break;
            }
            if (i == s.length() - 1) {
                value.append(s.charAt(i));
                root.val = Integer.parseInt(value.toString());
                return root;
            }
            value.append(s.charAt(i));
        }
        // 对左右子树递归求解
        int count = 1;  // 遇到左括号则++,遇到右括号则--
        for (int i = start + 1; i < s.length(); i++) {
            if ('(' == s.charAt(i)) count++;
            if (')' == s.charAt(i)) count--;
            if (count == 0) {
                // 找到了左子树的区间
                root.left = str2tree(s.substring(start + 1, i));
                if (i != s.length() - 1) {
                    // 剩余的就是右子树的区间
                    root.right = str2tree(s.substring(i + 2, s.length() - 1));
                }
                break;  // 左右子树整合完毕,结束
            }
        }
        return root;
    }
}

解题思路:

1.使用count来计数,确定左括号和 右括号的个数
2.确定根节点后,递归确定左子树和右子树

深度优先搜索
394. 字符串解码

题目描述

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:

输入:s = “3[a]2[bc]”
输出:“aaabcbc”
示例 2:

输入:s = “3[a2[c]]”
输出:“accaccacc”
示例 3:

输入:s = “2[abc]3[cd]ef”
输出:“abcabccdcdcdef”
示例 4:

输入:s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”

代码

class Solution {
    public String decodeString(String s) {
        //声明两个栈,一个存储结果,一个存储倍数
        LinkedList<String> stack_res = new LinkedList<>();
        LinkedList<Integer> stack_multi = new LinkedList<>();
        //初始化结果和倍数
        StringBuilder res = new StringBuilder("");
        int multi = 0;
        //遍历整个数组
        for(char c : s.toCharArray()){
            if(c == '['){//如果遇到“[”
                stack_res.addLast(res.toString());//将res入栈
                stack_multi.addLast(multi);//将倍数入栈
                res = new StringBuilder("");//重置res和倍数
                multi = 0;
            }else if(c == ']'){//如果遇到“]”
                StringBuilder tmp = new StringBuilder();
                int cur_multi = stack_multi.removeLast();
                for(int i = 0; i < cur_multi; i++){//将倍数栈最后一个元素n弹出,拼接n次res
                    tmp.append(res);
                }
                res = new StringBuilder(stack_res.removeLast() + tmp);//将结果栈最后一个元素弹出,拼接上一步的结果
            }else if(c >= '0' && c <= '9'){  //如果遇到数字,则将其赋予倍数
                multi = multi*10 + Integer.parseInt(c+"");
            }else{//如果是字母,则直接拼接res
                res.append(c);
            }
        }
        return res.toString();
    }
}

思路:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值