leetcode刷题记录

目录

目录

字符串

无重复字符的最长子串(力扣3)

最长连续序列(力扣128)

数组

缺失的第一个正数(力扣41)

合并区间(力扣56)

二叉树展开为链表(力扣114)

双指针

三数之和(力扣15)

回溯

括号生成(力扣22)

组合总和(力扣39)

分割回文串(力扣131)

动态规划

接雨水(力扣42)

乘积最大子数组(力扣152)

最长递增子序列(力扣300)

贪心

跳跃游戏 II(力扣45)

划分字母区间(力扣763)

链表

K个一组翻转链表(力扣25)

矩阵

螺旋矩阵(力扣54)


排序

搜索旋转排序数组(力扣33)

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

思路:见下面三条定理

//定理一:只有在顺序区间内才可以通过区间两端的数值判断 target 是否在其中。
//定理二:判断顺序区间还是乱序区间,只需要对比 left 和 right 是否是顺序对即可, left <= right ,顺序区间,否则乱序区间。
//定理三:每次二分都会至少存在一个顺序区间。

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length -1;

        while(left <= right) {
            int mid = left + (right-left)/2;
            if (nums[mid] == target) {
                return mid;
            }
            //left到mid是上升区间
            if (nums[left] <= nums[mid]) {
                if (target >= nums[left] && target <= nums[mid]) {
                    right = mid -1;
                } else {
                    left = mid + 1;
                }
            } else {
                //mid到right是上升区间
                if (target >= nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
}

在排序数组中查找元素的第一个和最后一个位置(力扣34)

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

思路:二分两次,一次找最左,一次找最右

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // first和last分别表示 首次 和 最后 出现的位置
        int first = -1;
        int last = -1;  
        while(left <= right) {
            int mid = left + (right - left)/2;
            if (nums[mid] == target) {
                first = mid;
                right = mid - 1;    //重点,不停向左缩放
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        left = 0;
        right = nums.length - 1;
        while(left <= right) {
            int mid = left + (right - left)/2;
            if (nums[mid] == target) {
                last = mid;
                left = mid + 1;  //重点,不停向右缩放
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return new int[]{first, last};
    }
    
}

寻找旋转排序数组中的最小值(力扣153)

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

思路:也是看顺序数组,最小节点一定在非顺序的那一侧

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;

        int result = nums[0];

        while(left <= right) {
            int mid = left + (right - left)/2;
            
            //左边是顺序数组,left->mid
            if (nums[left] <= nums[mid]) {
                result = Math.min(result, nums[left]);
                left = mid + 1;
            } else {
                //右边是顺序数组,mid->right
                result = Math.min(result, nums[mid]);
                right = mid -1;
            }
        }
        
        return result;
    }
}

字符串

无重复字符的最长子串(力扣3)

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

子串

 的长度。

示例 1:

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

示例 2:

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

示例 3:

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

思路:用一个map维护每个字母最后出现的下标

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length() <= 1) {
            return s.length();
        }
        int left = 0;
        int result = 0;
        Map<Character, Integer>  map = new HashMap<>();

        for (int i = 0; i< s.length(); i++) {
            char current = s.charAt(i);
            if (!map.containsKey(current)) {
                map.put(current, i);
            } else {
                //如'abba',遍历到第二个a时,如果不max,left会回退
                left = Math.max(left, map.get(current)+1); 
                map.put(current, i);
            }
            result = Math.max(result, i-left+1);
        }
        return result;
    }
}

最长连续序列(力扣128)

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

思路:

class Solution {
    public int longestConsecutive(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        Set<Integer> set = new HashSet<>();
        for(int i : nums) {
            set.add(i);
        }
        int result = 1;
        for (int i : nums) {
            if (set.contains(i-1)) {
                continue;
            }
            int temp = 1;
            while(set.contains(i+1)) {
                i++;
                temp++;
            }
            result = Math.max(result, temp);
        }
        return result;
    }
}

数组

缺失的第一个正数(力扣41)

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为  O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

思路:缺失的数一定在[1,N+1]的区间中,其中N为数组长度

  • 怎么理解:如果1在数组里,由于需要连续,假设[1,N]都在数组里,那么最坏情况N+1是缺失的最小数字。如果1不在数组里,那么1是缺失的最小数字
  • swap数组,把数字i放到i-1的位置。最后数组变成[1,2,3,4...N]
class Solution {
    public int firstMissingPositive(int[] nums) {
        if (nums.length == 0) {
            return 1;
        }
        for (int i = 0; i< nums.length; i++) {
            //nums[i]-1 应该与 i 交换,如果nums[0] = 3,则交换下标0与2
            while (nums[i]-1 >=0 && nums[i]-1 < nums.length) {
                if (nums[nums[i]-1] == nums[i]) {
                    break;
                }
                swap(nums, i, nums[i]-1);
            }
        }
        for (int i = 0; i< nums.length; i++) {
            if (nums[i] != i+1) {
                return i+1;
            }
        }
        return nums.length +1;
    }

    public void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

合并区间(力扣56)

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals.length == 0 || intervals[0].length == 0) {
            return new int[0][0];
        }

        Arrays.sort(intervals, new Comparator<int[]>(){
            public int compare(int[] a, int[] b) {
                return a[0] - b[0];
            }
        });

        List<int[]> list = new ArrayList<>();
        list.add(intervals[0]);
        for (int i = 1; i< intervals.length; i++) {
            int[] current = list.get(list.size() - 1);
            if (intervals[i][0] <= current[1]) {
                current[1] = Math.max(intervals[i][1], current[1]);
            } else {
                list.add(intervals[i]);
            }
        }

        int[][] result = new int[list.size()][2];
        for (int i = 0; i< list.size(); i++) {
            result[i][0] = list.get(i)[0];
            result[i][1] = list.get(i)[1];
        }
        return result;

    }
}

加油站(力扣134)

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

思路:

  1. 首先判断总gas能不能大于等于总cost,如果总gas不够,一切都白搭对吧(总(gas- cost)不用单独去计算,和找最低点时一起计算即可,只遍历一次);

  2. 再就是找总(gas-cost)的最低点,不管正负(当然如果最低点都是正的话那肯定能跑完了);也就是说:亏空最严重的一个点必须放在最后一步走,等着前面剩余的救助

  3. 找到最低点后,如果有解,那么解就是最低点的下一个点,因为总(gas-cost)是大于等于0的,所以前面损失的gas我从最低点下一个点开始都会拿回来!别管后面的趋势是先加后减还是先减后加,最终结果我是能填平前面的坑的。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int minPoint = Integer.MAX_VALUE;
        int minIndex = -1;
        int total = 0;

        for (int i = 0; i<gas.length; i++) {
            total = total + gas[i] - cost[i];
            if ( total <= minPoint) {
                minPoint = total;
                minIndex = i;
            }
        }
        if (total < 0) {
            return -1;
        }
        return (minIndex + 1) % gas.length;
    }
}


二叉树展开为链表(力扣114)

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:

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

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [0]
输出:[0]
class Solution {
    public void flatten(TreeNode root) {
        while(root != null) {
            if (root.left == null) {
                root =  root.right;
            } else {
                TreeNode pre = root.left;
                while(pre.right != null) {
                    pre = pre.right;
                }
                pre.right = root.right;
                root.right = root.left;
                root.left = null;
                root = root.right;
            }
        }
    }
}

双指针

三数之和(力扣15)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

思路:. - 力扣(LeetCode)

  • 细节:排序后如[-1,-1,0,1,1],如果前一个数与当前数相同,需要忽略,不然会重复
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length < 3) {
            return result;
        }

        Arrays.sort(nums);
        for (int i = 0; i < nums.length-2; i++) {
            //如果前一个数重复了,忽略跳过
            if (i!=0 && nums[i-1] == nums[i]) {
                continue;
            }
            int left = i+1;
            int right = nums.length -1;
            while(left < right) {
                //如果前一个数重复了,忽略跳过
                if (left != i+1 && nums[left] == nums[left-1]) {
                    left++;
                    continue;
                }
                //如果前一个数重复了,忽略跳过
                if (right != nums.length-1 && nums[right] == nums[right+1]) {
                    right--;
                    continue;
                }
                int temp = nums[left] + nums[right] + nums[i];
                if(temp > 0) {
                    right--;
                } else if (temp < 0) {
                    left++;
                } else {
                    result.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
}

回溯

括号生成(力扣22)

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

思路:剩余左括号总数要小于等于右括号

class Solution {
        List<String> res = new ArrayList<>();
        public List<String> generateParenthesis(int n) {
            if(n <= 0){
                return res;
            }
            getParenthesis("",n,n);
            return res;
        }
        
        //left,right分别表示剩余的左括号,右括号数量
        private void getParenthesis(String str,int left, int right) {
            //左右括号全部用完时,加入res
            if(left == 0 && right == 0 ){
                res.add(str);
                return;
            }
            if(left == right){
                //剩余左右括号数相等,下一个只能用左括号
                getParenthesis(str+"(",left-1,right);
            }else if(left < right){
                //剩余左括号小于右括号,下一个可以用左括号也可以用右括号
                if(left > 0){
                    getParenthesis(str+"(",left-1,right);
                }
                getParenthesis(str+")",left,right-1);
            }
        }
    }

组合总和(力扣39)

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

思路:. - 力扣(LeetCode)

  • 先对数组排序,然后回溯
class Solution {

    List<List<Integer>> result = new ArrayList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if (candidates.length == 0) {
            return result;
        }
        //对数组先排序,方便回溯时剪枝
        Arrays.sort(candidates);
        backtrack(0, new ArrayList<>(), target, candidates);
        return result;
    }

    public void backtrack(int start, List<Integer> current, int target, int[] candidates) {
        // 当前的数组,已经满足和为target,加入答案里
        if (target == 0) {
            result.add(new ArrayList<>(current));
            return;
        }
        // 遍历所有选择
        // 剪枝二:从 start 开始遍历,避免生成重复子集
        for (int i = start; i< candidates.length; ++i) {
            // 剪枝一:若子集和超过 target ,则直接结束循环
            // 这是因为数组已排序,后边元素更大,子集和一定超过 target
            if (candidates[i] > target) {
                return;
            }
            //开始回溯
            current.add(candidates[i]);
            //之所以是i,不是i+1,是因为数组元素可以重复使用
            backtrack(i, current, target - candidates[i], candidates);
            current.remove(current.size() - 1);
        }
    }
}

分割回文串(力扣131)

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

思路:. - 力扣(LeetCode)

  • 先生成boolean的dp[][],dp[i][j]表示字符串的[i,j]是否是回文的
  • 再对dp进行回溯
class Solution {

    List<List<String>> result = new ArrayList<>();

    public List<List<String>> partition(String s) {
        if (s.length() == 0) {
            return result;
        }
        if (s.length() == 1) {
            List<String> temp = new ArrayList<>();
            temp.add(s);
            result.add(temp);
            return result;
        }
        //先生成二维回文的动态规划
        boolean[][] dp = generateDp(s);
        //回溯
        backTrack(0, new ArrayList<>(), dp, s);
        return result;

    }

    //dp[x][y]表示字符串[x,y]是否是回文的
    public boolean[][] generateDp(String s) {
        int length = s.length();
        boolean[][] dp = new boolean[length][length];

        //i代表长度,j代表从第j位开始循环遍历
        for (int i = 1; i <= length; i++) {
            for (int j = 0; j< length; j++) {
                int end = j + i -1;
                if (end >= length) {
                    break;
                }
                if (i == 1) {
                    dp[j][j] = true;
                } else if (i == 2) {
                    dp[j][end] = s.charAt(j) == s.charAt(end);
                } else {
                    dp[j][end] = dp[j+1][end-1] && s.charAt(j) == s.charAt(end);
                }
            }
        }
        return dp;
    }

    public void backTrack(int start, List<String> current, boolean[][] dp, String s) {
        //string中所有字母已经处理完成,加到答案中
        if (start == s.length()) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        for (int i = start; i < s.length(); ++i) {
            //如果从第start位到第i位,不是回文的:代表行不通,直接contine
            if (!dp[start][i]) {
                continue;
            } else {
                //如果从第start位到第i位,是回文的:从i+1开始回溯
                current.add(s.substring(start, i+1));
                backTrack(i+1, current, dp, s);
                current.remove(current.size()-1);
            }          
        }
    }
}

动态规划

接雨水(力扣42)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

思路:二维dp

class Solution {
    public int trap(int[] height) {
        if (height.length == 0) {
            return 0;
        }

        int[] left = new int[height.length];
        int[] right = new int[height.length];
        left[0] = height[0];
        right[height.length-1] = height[height.length-1];
        
        int j;
        for (int i = 1; i< height.length; i++) {
            j = height.length -i -1;
            left[i] = Math.max(left[i-1], height[i]);
            right[j] = Math.max(right[j+1], height[j]);
        }
        int result = 0;
        for (int i = 0; i < height.length; i++) {
            result += Math.min(left[i],right[i]) - height[i];
        }
        return result;
    }


乘积最大子数组(力扣152)

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续

子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

思路:同和最大子数组,dp[i]表示以第 i个元素结尾的乘积最大子数组的乘积

class Solution {
    public int maxProduct(int[] nums) {
        int[] dpmax = new int[nums.length];
        int[] dpmin = new int[nums.length];
        dpmax[0] = nums[0];
        dpmin[0] = nums[0];
        int result = nums[0];

        for (int i = 1; i < nums.length; ++i) {
            dpmax[i] = Math.max(Math.max(dpmax[i-1] * nums[i], dpmin[i-1] * nums[i]), nums[i]);
            dpmin[i] = Math.min(Math.min(dpmax[i-1] * nums[i], dpmin[i-1] * nums[i]), nums[i]);
            result = Math.max(result, dpmax[i]);
        }
        return result;
    }
}

最长递增子序列(力扣300)

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

思路:. - 力扣(LeetCode)的方法一

  • 定义dp[i]为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度
  • 例子:输入:nums = [10,9,2,5,3,7,101,18],dp = [1,1,1,2,2,3,4,4]
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        //定义一个result,在后面for循环中会不断更新result
        int result = 1;

        int[] dp = new int[nums.length];
        dp[0] = 1;

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

贪心

跳跃游戏 II(力扣45)

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

示例 1:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]
输出: 2

思路:. - 力扣(LeetCode)贪心,或者DP

//DP
class Solution {
    public int jump(int[] nums) {
        if (nums.length <= 1) {
            return 0;
        }
        int[] dp = new int[nums.length];
        Arrays.fill(dp, nums.length-1);
        dp[0] = 0;
        for (int i = 1; i< nums.length; i++) {
            for (int j = i-1; j>=0; j--) {
                if (j+nums[j] >= i) {
                    dp[i] = Math.min(dp[i], dp[j]+1);
                }
            }
        }
        return dp[nums.length-1];
    }
}

//贪心
class Solution {
    public int jump(int[] nums) {
        int length = nums.length;
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;
        for (int i = 0; i < length - 1; i++) {
            maxPosition = Math.max(maxPosition, i + nums[i]); 
            if (i == end) {
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }
}

划分字母区间(力扣763)

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

思路:维护一个int[26]数组,记录每个字母最后出现的位置

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> result = new ArrayList<>();
        if (s.length() == 0) {
            return result;
        }
        int[] nums = new int[26];
        for (int i = 0; i < s.length(); i++) {
            nums[s.charAt(i)-'a'] = i;
        }
        
        //开始遍历
        int start = 0;
        int end = 0;
        for (int i = 0; i< s.length(); i++) {
            end = Math.max(end, nums[s.charAt(i)-'a']);
            if(i == end) {
                result.add(end- start +1);
                start = end +1;
            }
        }
        return result;
    }
}

链表

K个一组翻转链表(力扣25)

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

思路:. - 力扣(LeetCode)递归

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode tail = head;
        for (int i = 1; i< k; i++) { //选取k个节点,需要向后移动了k-1次
            if (tail == null) {
                return head;
            }
            tail = tail.next;
        }
        if (tail == null) {  //这里需要对第k个节点特判,否则会空指针
            return head;
        }
        ListNode next = tail.next;  //next指向第k+1个节点
        tail.next = null;
        ListNode newHead = reverse(head);
        head.next = reverseKGroup(next, k);
        return newHead;
        
    }

    //反转链表
    private ListNode reverse(ListNode head) {
        if (head == null) {
            return head;
        } 
        ListNode pre = null;
        while(head != null) {
            ListNode temp = head.next;
            head.next = pre;
            pre = head;
            head = temp;
        }
        return pre;
    }
}

矩阵

螺旋矩阵(力扣54)

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> list = new ArrayList<>();
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return list;
        }

        int left =0;
        int right = matrix[0].length -1;
        int up = 0;
        int down = matrix.length - 1;
        while(true) {
            for (int i =left; i<= right; i++) {
                list.add(matrix[up][i]);
            }
            if (up+1>down) break;
            up++;
            for (int i = up; i<= down; i++) {
                list.add(matrix[i][right]);
            }
            if (left+1>right) break;
            right--;
            for (int i = right; i>=left; i--) {
                list.add(matrix[down][i]);
            }
            if (up+1>down) break;
            down--;
            for (int i = down; i>=up; i--) {
                list.add(matrix[i][left]);
            }
            if (left+1>right) break;
            left++;
        }
        return list;
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值