力扣刷题记录

文章目录

  • 1.两数之和:
  • 2.数组:
  • 3. 最小栈:
  • 4.动态规划问题:
  • 5.贪心算法:

1.两数之和:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

  1. 暴力破解:
    第一个循环为什么-1是因为第一次循环可以不循环最后一个元素
    public int[] twoSum(int[] nums, int target) {
        for(int i = 0;i<nums.length-1;i++){
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]+nums[j]==target){
                    return new int[]{i,j};
                }
            }
        }
        return null;
    }
}
  1. O(n)解法
    原来的思路:也是用target-当前循环到的数组的值,判断这个得到的结果在不在数组中,遇到的问题是发现用数组不好操作,因为int[ ]是一个长度不可变的数组,不好添加元素进去,也不好根据值获得下标。
    正确思路:添加一个map集合,存放值和下标
	public class leedcode1_TwoNumsSum{
    public int[] sum(int[] arr,int target){
        Map<Integer, Integer> map = new HashMap<>(arr.length-1);
        map.put(arr[0],0);
        for (int i = 1; i < arr.length; i++) {
            int a =target - arr[i];
            if (map.containsKey(a)){
                return new int[]{map.get(a),i};
            }
            map.put(arr[i],i);
        }
        return null;
    }
}

注:map函数可以使用get方法通过键取到值,不方便从值获取到键,所以设置数组的下标为值,数组内容为键。

2.数组:

二分查找:
前提:数组中元素不重复并且是一个有序数组。
问题:其实二分查找中最复杂、最难弄的就是边界的选择

1.704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

思路:创建3个变量:first、middle、last,first和last代表着middle的区间,将middle与target比较,根据比较结果缩小区间。
我自己原来的写法:
第一是对循环条件的判断有问题,反正就是很多问题就是了。

	public int search(int[] nums, int target) {
        int first = 0,last = nums.length;
        int middle = -1;
        boolean flags = true;

        while (flags){
            middle =(first+last)/2;
            if (nums[middle] == target){
                return middle;
            }else if(first == middle || last == middle){
                return -1;
            } else if (middle < target){
                first = middle;
            }else {
                last = middle;
            }
        }
        return middle;
    }
  • 第一种解法:两边闭区间法:即target的范围在[first , last]
    此时需要注意:while的判断范围是 first <= last,因为相等的情况是有意义的,因为target可能为first可能为last;并且每次判断时 都判断了middle的值不是target,所以在区间的移动时要赋值first = middle+1;而不是 first = middle,last同理。
	public int search(int[] nums, int target) {
        int first = 0, last = nums.length-1;
        int middle = -1;
        while (first <= last) {
            middle =first + (last - first) / 2;
            if (nums[middle] > target) {
                last = middle -1;
            } else if (nums[middle] < target) {
                first = middle+1;
            } else {
                return middle;
            }
        }
        return -1;
    }
  • 第二种解法:左闭右开法:target在 [first,last)区间内;
    首先判断条件内,first == last就没有意义了,因为两者相等的时候说明target两者都取不到,所以判断条件是 first < last;其次区间变化的公式也不一样,last= middle,first = middle+1,因为区间变化说明middle和target不相等,在不相等的情况下此时的middle值对于target来说已无意义。
	public int search(int[] nums, int target) {
        int first = 0, last = nums.length;
        int middle = -1;
        while (first < last) {
            middle =first + (last - first) / 2;
            if (nums[middle] > target) {
                last = middle;
            } else if (nums[middle] < target) {
                first = middle + 1;
            } else {
                return middle;
            }
        }
        return -1;
    }

2.217.存在重复元素
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
示例 1:
输入:nums = [1,2,3,1]
输出:true

思路:这题我拿到手,先用的是暴力解法,写出来后发现超限了。

	public boolean containsDuplicate(int[] nums) {
        for (int i = 0; i < nums.length -1; i++) {
            for (int j = i; j < nums.length; j++) {
                if (nums[i] == nums[j]){
                    return true;
                }
            }
        }
        return false;
    }

正确解法:
1.使用排序,将元素排好序后只要判断当前元素和下一元素是否相同就行。

	public boolean containsDuplicate(int[] nums) {
        Arrays.sort(nums);
        for (int i = 0; i < nums.length-1; i++) {
            if(nums[i] == nums[i+1]){
                return true;
            }
        }
        return false;
    }

2.使用hash表,因为hash表不允许元素重复,所以可以起到查重效果。

class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set set = new HashSet();
        for (int num : nums) {
            set.add(num);
        }
        if (set.size() == nums.length){
            return false;
        }
        return true;
    }
}

优化后:

class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set set = new HashSet();
        for (int num : nums) {
            if (set.contains(num)) {
                return true;
            }
            set.add(num);
        }
        return false;
    }
}

3.88.合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

思路:
我的思路:看了一些正确答案的思路后,我明白了使用双指针的解法,就是分别建立三个指针和一个copy nums1的数组,将两个数组中的值进行比较,因为两个数组都是顺序的,如果有一方的值较小,就存入到我们新建的数组中。
我就写了下面的写法,但是总是会报越界异常,自己实践后发现:在特殊情况:当某一数组元素比另一个数组元素都要小时,最后会出现一个数组已经遍历到头了,另一个数组还没开始遍历,这使就会出问题。

    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=0,j=0,p = 0;
        int[] sore = new int[m+n];

        while (i<m || j<n){
            if(nums1[i]<=nums2[j]){
                sore[p] = nums1[i];
                i++;
            }else if (nums1[i]>nums2[j]){
                sore[p] = nums2[j];
                j++;
            }
            p++;
        }
        for (int k = 0; k < m + n; k++) {
            nums1[k] = sore[k];
        }
    }

4.349.两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

思路:题目中要求是返回交集,那我们可以想到,交集就是两个元素中重复的元素,可以利用哈希表中不存储重复元素的特点,检验重复值,下面是我写的方法,但我忽略了一点:交集元素也要是唯一的,但我是用list集合存储的,无法确保唯一性。
我还发现一个问题,我真的是太傻了,我使用这种方法if (!hashSet.add(nums2[i])) 判断是否存在重合元素,却没有考虑过会把新的元素给添加到hashset集合中导致结果出错。

	public int[] intersection(int[] nums1, int[] nums2) {
        //我是使用list集合来存储交集元素的,但是发现list元素会存储相同元素,所以会报错
        ArrayList<Object> list = new ArrayList<>();
        int i = 0;
        //定义了一个hashset来存储第一个数组
        HashSet<Integer> hashSet = new HashSet<>();
        for (int n :nums1) {
            hashSet.add(n);
        }
//        将第二个数组的值存入set集合中,使用set集合元素不能重复的特点进行判断。
        while (i<nums2.length){
            if (!hashSet.add(nums2[i])){
                list.add(nums2[i]);
            }
            i++;
        }
        int[] intersection = new int[list .size()];
        int index = 0;
        for (int num : list ) {
            intersection[index++] = num;
        }
        return intersection;
    }

只要把 if (!hashSet.add(nums2[i])){ 改为 if (hashSet.contains(nums2[i])){ 就行了,我真的是,人家都提供了现成的方法了我还不使用。但终于主要靠自己写出了一题,开心!
还有一种用双指针解决的,思路是先将数组排序好,再使用双指针进行比较,但是我觉得很麻烦且不好理解。

5.350.两个数组的交集 II
https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/solution/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution/
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

题目理解:其实这题题目有点绕,题目意思是说最后返回的数组中元素出现的次数是和此元素在两个数组中分别出现的次数挂钩的,以少的为主,比如说示例1:元素2在两个数组中都出现了两次,所以结果是[2,2]。示例2:元素4和9在nums1中只分别出现了1次,所以最后结果中都只有1次。
思路:其实这题就是在前一题的基础上增加了计算并判断交集元素重复个数的要求,我就想到了使用map集合,key存储元素,value存储出现的次数。将两个数组都转换为map集合,最后循环一个map集合获得其所有key值,对此key值是否存在在另一个map集合中做出判断,如果存在就比较其value值的大小,以更小的为主添加元素。
虽然写出来了但是效率不高,我创建了两个map集合,一个list集合,一个数组,还是用了很多的for循环

public int[] intersect(int[] nums1, int[] nums2) {
        HashMap<Integer, Integer> map1 = getMap(nums1);
        HashMap<Integer, Integer> map2 = getMap(nums2);

        ArrayList<Integer> list = new ArrayList<>();

        Set<Integer> set = map2.keySet();
        for (int key:set) {
            //用 num 记录比较
            int num = 0;
            if (map1.containsKey(key)){
                num = Math.min(map1.get(key),map2.get(key));
                for (int i = 0; i < num; i++) {
                    list.add(key);
                }
            }
        }
        int[] ints = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            ints[i]=list.get(i);
        }
        return ints;
    }

    //将数组转换为map集合
    public HashMap getMap(int[] num){
        //这里的 map 存储的是元素出现的次数:key表示数组元素,value表示此元素出现的次数
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int n :num) {
            if (map.containsKey(n)){
                Integer val = map.get(n);
                map.put(n,++val);
            }else {
                map.put(n,1);
            }
        }
        return map;
    }

思路:
仅创建一个map集合,用于存储长度较小的数组中元素出现的次数,再创建一个数组,遍历另一个数组中的元素,每当出现和map集合中的key相同的元素时,这个key对应的val就 减1,如果对应的val值大于0,则将这个key添加到数组中。
官方使用hashmap的答案,为什么要交换两个数组?因为如果以较小数组元素为主创建map函数可以有效降低内存空间的使用。

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return intersect(nums2, nums1);
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int num : nums1) {
            int count = map.getOrDefault(num, 0) + 1;
            map.put(num, count);
        }
        int[] intersection = new int[nums1.length];
        int index = 0;
        for (int num : nums2) {
            int count = map.getOrDefault(num, 0);
            if (count > 0) {
                intersection[index++] = num;
                count--;
                if (count > 0) {
                    map.put(num, count);
                } else {
                    map.remove(num);
                }
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

3. 最小栈:

题目如下:
力扣刷题记录_第1张图片
首先这个测试示例我实在没有看懂,加上我有点不清楚它让我做什么,我就先用数组实现了一个栈,在栈的getMin 方法中进行for循环判断哪个值是最小值。
这明显是不符合题意的,因为题目中写到“要在常数时间内检查到最小值”,如果使用for循环明显不能符合题意。
官方正确答案:使用双栈操作。
思路:创建两个栈,一个栈存储加进来的数据,另一个栈储存最小值,因为栈有先进后出的特点,所以如果从数据栈中pop出来的不是 min栈 中的最顶层就是最小值的元素,则说明最小值还在数据栈中,则说明 min栈不需要pop元素。

class MinStack {
    Stack<Integer> minStack;
    Stack<Integer> dataStack;

    public MinStack() {
       minStack = new Stack();
       dataStack = new Stack();
    }
    public void push(int val) {
        dataStack.push(val);
        if(minStack.isEmpty() || val <= minStack.peek()){
            minStack.push(val);
        }
    }
    public void pop() {
         int x = dataStack.pop();
         if(x == minStack.peek()){
            minStack.pop();
        }
    }
    public int top() {
        return dataStack.peek();
    }
    public int getMin() {
        return minStack.peek();
    }
}

注意点:这道题并不是想让我们手写栈,所以可以直接使用对应的方法即可,我原来不知道这一点。其中 peek方法是获得栈顶部的元素,其他相同。

我的写法:

class MinStack {
    
    Integer[] array;
    int size;

    public MinStack() {
        array = new Integer[10];
    }

    public void push(int val) {
        if (size < array.length) {
            array[size] = val;
            size++;
        } else {
            //动态数组
            int newSize = 2 * (size + 1);
            Integer[] newArray = new Integer[newSize];
            for (int i = 0; i <= size; i++) {
                newArray[i] = array[i];
            }
            array = newArray;
            size = newSize;
        }
    }

    public void pop() {
        if (size != 0) {
            size--;
        } else {
            System.out.println("没有可以删除的数据");
        }
    }

    public int top() {
        return array[size];
    }

    public int getMin() {
        int minNum = array[0];
        for (int i = 0; i <= size; i++) {
            if (array[i] < minNum) {
                minNum = array[i];
            }
        }
        return minNum;
    }

    public static void main(String[] args) {
        MinStack obj = new MinStack();
        obj.push(-3);
        obj.pop();
        obj.push(0);
        obj.pop();
        obj.push(-2);
        int param_3 = obj.top();
        int param_4 = obj.getMin();
    }
}

4.动态规划问题:

动态规划,就是让在变化的范围中去进行求解,又被称为带备忘录的递归,因为一般情况会定义一个变量或者数组来记录某一变化的辅助值,帮助我们进行解决问题。是一个用空间换时间的算法。

  1. 买卖股票的最佳时机

题目及案例:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

思路:
首先解读题目,我们需要求的就是数组中元素的最大差 A - B=MAX,条件是A在数组中在B的后面。
第i天的最大收益 = max { 第i-1天的最大收益,第 i 天的收益 - 前(i-1)天中的最小价格},就是说最大收益就是比较以前的最大收益当前值的最大收益之间的最大值,既然需要求当前值的最大收益就需要知道当前值之前的最小值是多少。
因为写过一遍了,隐约记得思路是:我定义两个变量,一个变量minNum用于存储数组的最小值,另一个变量res用于存储的当前的差值:
最后虽然可以成功运行但是效率好像不是很高。

public int maxProfit(int[] prices) {
        int length = prices.length;
        if (length == 0){
            return 0;
        }
        //最小值
        int num = prices[0];
        //差值
        int res = 0;
        for (int i = 1; i < length; i++) {
            num = Math.min(num,prices[i]);
            res = Math.max(res,prices[i]-num);
        }
        return res;
    }

看一下官方答案,只使用了 1秒 就解决了
因为如果是空数组是不会进入for循环,直接返回 maxprofit为0,所以可以省略一个 if 判断。其他就没有什么区别了。

public class Solution {
    public int maxProfit(int prices[]) {
        int minprice = Integer.MAX_VALUE;
        int maxprofit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minprice) {
                minprice = prices[i];
            } else if (prices[i] - minprice > maxprofit) {
                maxprofit = prices[i] - minprice;
            }
        }
        return maxprofit;
    }
}
  1. 最大连续子数组:

题目及案例:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1

思路:
首先解读题目,就是找到数组中连续最大的元素之和。
在对下一元素进行遍历时肯定要先判断添加了下一元素后的最大和 和 之前的最大和 的大小关系;并且如果下一个元素比存储的最大和还要大,则选择重新开始计算最大和。
我按照这个思路写了一段代码,发现并不对,动态规划的难点就在于数据是在不断变化的,我这里写的只能计算两个值中的最大值情况,没有考虑到当第三个值加入后可能会比一开始的最大值更大。

public int maxSubArray(int[] nums) {
        int length = nums.length;
        if (length == 0 ){
            return 0;
        }
        int res = nums[0];
        for (int i = 1; i < length; i++) {
            if (nums[i] > res+nums[i]){
                res = nums[i];
            }else {
                res = Math.max(res,res+nums[i]);
            }
        }
        return res;
    }

发现了一位大佬的写法,我和他的思路差不多,但我的就是有问题
我发现我的思路和正确答案之间差了一个关键步骤,就是我算出来的只是一个局部最优解,算出来的只是 res 和 res + nums[i] 之间的最优解而已,并不是整体的一个最优解,最后一个 if 判断将结果进行更新后才是整体的最优解。

//用 temp 记录局部最优值,用 result 记录全局最优值。
class Solution {
    public int maxSubArray(int[] nums) {
        int result = nums[0];
        int temp = nums[0];

        for(int i = 1; i < nums.length; i++) {
        //如果加上新值后的结果比新的值大,则将新值加进结果中,如果加上新值后还是新值大,则以新值为头重新开始。
            if(temp + nums[i] >= nums[i]) {
                temp = temp + nums[i];
            } else {
                temp = nums[i];
            }

            if(temp > result) {
                result = temp;
            }
        }
        return result;
    }
}

官方答案:
官方的方法就更简单了,用一句话就完成了我的 if else的判断,再用一句话完成了对整体最优解的更新。值得学习。

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0, maxAns = nums[0];
        for (int x : nums) {
            pre = Math.max(pre + x, x);
            maxAns = Math.max(maxAns, pre);
        }
        return maxAns;
    }
}

使用动态规划数组解决的方法:

    /**
     * @Description: 动态方程解决版
     * dp[i]:以nums[i]结尾的连续子数组的最大和,因为dp[i]中列出了以每个元素结尾的最大和的所有可能,
     * 所以需要res变量取到dp数组中的最大元素,这才是我们需要的答案。
     * @author: wyh
     * @date: 2022/4/22 19:37
     */
    public int maxSubArray(int[] nums) {
        int length = nums.length;
        int[] dp = new int[length];
        dp[0] =nums[0];
        int res = nums[0];
        for (int i = 1; i < length; i++) {
            dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            res = Math.max(dp[i],res);
        }
        return res;
    }
  1. 打家劫舍问题

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。

思路:
首先解读题目,这题和连续最大子数组问题有点相像,这题是算数组元素的最大和,条件是元素不能是连续的。
通过我的思考,我发现这道题目的关键就出在,因为数组中没有负数,问题的关键就在相加的第二个元素是选择第3位的元素还是第4位的元素(因为如果选择隔了5位的元素,因为元素没有负值,所以完全可以添加第3位元素,最后的和会更高)
力扣刷题记录_第2张图片
我自己的错误代码,首先思路有点靠边,但还是有点问题,对比下面的正确思路,问题在哪呢?
首先我想通过一个值来存储每次的最大金额,很明显这有点困难,因为这个设计到的元素很多,其次其实我是用从前往后的方法算的,就是用当下的值算后面的值,设计的不好会出现越界异常的问题,但是答案相当于从后往前计算,这样不会出现越界问题,又方便。

    public int rob(int[] nums) {
        int tem = 0;
        int res = 0;
        int size = nums.length - 1;
        //越界异常
        for (int i = 0; i < size; i++) {
            int num = nums[i];
            if (i >= nums.length-3){
                tem = tem+nums[size];
            }
            tem = Math.max(num +nums[i+2], num+nums[i+3]);
            if(res<tem){
                res =tem;
            }
        }
        return res;
    }

正确思路:
将问题转换为:是否偷着当前的第 i 家。
动态规划dp[i] 代表正准备偷第 i 家时的最高金额,为什叫正准备偷呢,就是我们动态方程就是在判断是否偷这个第 i 家。
初始值:dp[0] = nums[0] 表示还没开始偷,dp[1] =max{nums[0],nums[1]}
动态方程:dp[i] = max{dp[i-1],dp[i-2]+nums[i]}
解释:如果上一家偷了,那最大值就是偷完上一家的最大值;如果昨天没偷,那最大值是前家的最大值 + 这一家的最大值。

    public int rob(int[] nums) {
        int length = nums.length;
        if (length <=1){
            return nums[0];
        }
        int[] dp = new int[length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        
        for (int i = 2; i < length; i++) {
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);           
        }
        return dp[length-1];
    }
  1. 爬楼梯问题:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 2:
输入:n = 3
输出:3

思路:这题我们简化为,爬到第 i 阶时有两种情况,一种是从第i-1阶爬上来的,一种是从第 i-2 阶爬上来的,所以爬到第 i 阶的方法是爬到第 i-1 阶的方法加上爬到第 i-2 阶的方法之和。
设定一个动态规划方程 dp[i]:表示爬到第i阶的方法数: dp[i] = dp[i-1] + dp[i -2]

public int climbStairs(int n) {
        int res = 0;
        if (n<=1){
            res = n == 0?0:(res=1);
            return  res;
        }
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i < n; i++) {
            dp[i] = dp[i-2]+dp[i-1];
        }
        res = dp[n];
        return res;
    }

官方解法:使用滑动数组的思想做

class Solution {
    public int climbStairs(int n) {
        int p = 0, q = 0, r = 1;
        for (int i = 1; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
}
  1. 斐波那契数:

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。
也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

思路:和上面爬楼梯问题一样,可以使用滑动数组的方法
疑问:为什么 r = p+q;要放在后面。

class Solution {
    public int fib(int n) {
        int p = 0 ;
        int q =1;
        
        if (n <=1 ){
            return n==0?0:1;
        }
        
        int r = 1;
        for (int i = 2; i < n; i++) {
            p =q;
            q =r;
            r = p+q;
        }
        return r;
    }
}

5.不同路径问题:

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

思路:
本题是动态规划的思想,将问题转换为走到具体某一步路径的方法。

	public int uniquePaths(int m, int n) {
        int[][] trackNum = new int[m][n];
        trackNum[0][0] = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {

                if (i == 0 || j == 0){
                    trackNum[i][j] = 1;
                }else{
                    trackNum[i][j] = trackNum[i-1][j]+trackNum[i][j-1];
                }

            }
        }
        return trackNum[m-1][n-1];
    }
  1. 392.判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1:
输入:s = “axc”, t = “ahbgdc”
输出:false

思路:
我的思路是利用一个数组记录两个字符串中相同的字符的下标,然后通过判断数组中是否有连续的从小到大的连续元素来得知是否有子序列。后续发现问题我又对其进行了优化,比如说当数据不相同时就在数组中存入t.length()+1,没有值会比这个值更大。但当时我就想到了一个问题,就是我们有两个变量,得用二维数组进行存储,当时立马觉得不可能。

    public static void main(String[] args) {
        判断子序列 b = new 判断子序列();
        System.out.println(b.isSubsequence("axc", "ahbgdc"));
    }
    public boolean isSubsequence(String s, String t) {
        int[] ints = new int[t.length()];
        if (t.length() == 1 ){
            return s.contains(t);
        }
        for (int j = 0; j < s.length(); j++) {
            for (int i = 0; i < t.length(); i++) {
            //我的代码在下面这个存值的这块是有很大问题的。
                if (t.charAt(i) == s.charAt(j)){
                    ints[j] = i;
                }else {
                    ints[j] = t.length()+1;
                }
            }
        }
        for (int i = 1; i < ints.length; i++) {
            if (ints[i] > ints[i-1]){
                return true;
            }
        }
        return false;
    }

官方解答:
这个方法是用双指针来做的,没想到动态规划的方法反而成为了暴力解法,更加的复杂难懂。这个事情也告诉了我们,虽然题目上写的是属于动态规划,但有的题目用动态规划反而更难了。
思路就是,使用两个变量分别代表两个字符串的指针,当s和t中有相同的字母时,指向s的指针才会向下移动一位,当作为s变量指针的 i 的大小等于s的长度时,则说明s中有t的子序列,因为如果s中没有t的子序列,则s变量指针的 i 的大小肯定是小于s的长度,因为有字母没有匹配上。

class Solution {
    public boolean isSubsequence(String s, String t) {
        int n = s.length(), m = t.length();
        int i = 0, j = 0;
        while (i < n && j < m) {
            if (s.charAt(i) == t.charAt(j)) {
                i++;
            }
            j++;
        }
        return i == n;
    }
}
  1. 746.使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15

思路,这题其实和打家劫舍问题很类似,将“达到楼梯顶部的最低花费问题” 转化为到达第 i 个台阶的最低花费,到达第 i 个台阶的最低花费有两种情况:第一种是从第i-1个台阶上来,第二种是从第 i-2 个台阶上来,都加上本来台阶的花费即可。
就这么简单的思路我还想了很久,要注意下标越界的问题,因为要达到楼顶应是给定数组加1。

	public int minCostClimbingStairs(int[] cost) {
        int length = cost.length;
        int[] dp = new int[length+1];
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= length; i++) {
            dp[i] = Math.min(cost[i-1]+dp[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[length];
    }

使用滑动数组的方法可以省略掉数组的创建部分,但是滑动数组我实在是有点不能理解。

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int prev = 0, curr = 0;
        for (int i = 2; i <= n; i++) {
            int next = Math.min(curr + cost[i - 1], prev + cost[i - 2]);
            prev = curr;
            curr = next;
        }
        return curr;
    }
}

5.贪心算法:

思路:由局部最优解 =》 整体最优解

  1. 55.跳跃游戏

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

首先我的思路:所有的情况都是要从第一个元素出发跳跃的,但第一个元素可能跳的结果不同,有 nums[1]种跳法,我循环遍历nums[1],计算出每种跳法的剩余步数 nums.length-i,以及下一步可走的长度:nums[i] ,再对下一步的 nums[i] 的长度进行一个遍历,同步更新剩余步数 和可走长度,如果可走长度大于了剩余步数,就表示可以达到数组的最后一个元素。
官方思路:每一步都有一个可达到的最大长度,最大长度之前的所有地方都是可以达到的,通过遍历数组元素更新可到达的最大长度,如果遍历的 i 比最大长度大,说明此时 i 前肯定有一个为0的元素,导致停住了无法往前走,则说明这个数组无法到达最后一个元素。
正确代码:

class Solution {
    public boolean canJump(int[] nums) {
        int index = 0;
        for (int i = 0; i < nums.length; i++) {
            if(i>index){
                return false;
            }
            index = Math.max(i+nums[i],index);
        }
        return true;
    }
}

优化版本:
省去了一个 if 判断,当i>index的时候,直接不满足for循环条件。

class Solution {
    public boolean canJump(int[] nums) {
        int index = 0;
        for (int i = 0; i <= index && index<nums.length; i++) {
            index = Math.max(i+nums[i],index);
        }
        return index>=nums.length-1;
    }
}
  1. 122.买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回你能获得的 最大 利润 。
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。

思路:这是一道我在动态规划中看到的题目,但是他使用贪心的思想会更简便一点。
我的思路:当每次第i个元素大于第i+1个元素时,就将股票卖出,通过每次更新最小值来获得最大的收益。
我的代码:这段代码是错误的,因为分为两种情况:只卖了一次和卖了n次的情况,他们的result和middle的关系是不同的;还有一个情况是长度问题,即使我再if中添加了判断,依旧会在编译期报越界异常的错。

class Solution {
    public int maxProfit(int[] prices) {
        int sum =0;
        int min = prices[0];
        int middle = 0;
        int result = 0;
        for (int i = 1; i < prices.length; i++) {
            min = Math.min(min,prices[i]);
            middle = Math.max(middle,prices[i]-min);
//            卖出了一次就相当于需要初始化一次
            if (i<=prices.length-2 & prices[i]>prices[i+1]){
//                说明卖出了一次
                result += middle;
//                更新min
                min = prices[i+1];
                middle =0;
                ++sum;
            }
        }
        if(sum == 0 ){
            // middle = Math.max(middle,prices[prices.length-1]-min);
            return middle;
        }
        return result;
    }
}

正确思路:我只想到了第一步,而没有想到第一步后面最关键的一步
力扣刷题记录_第3张图片
其实我还是不是很理解为什么只加每天的正利润就可以,就是即理解又不理解的感觉。
力扣刷题记录_第4张图片

你可能感兴趣的:(面试,leetcode,算法)