LeetCode 热题 HOT 100(P11~P20)

文章详细介绍了LeetCode中的多个热门问题,涉及括号的有效性检查、链表操作、生成括号组合、合并链表等,主要运用了栈、递归和动态规划的解题技巧。
摘要由CSDN通过智能技术生成

 系列文章: 

LeetCode 热题 HOT 100(P1~P10)-CSDN博客

LeetCode 热题 HOT 100(P11~P20)-CSDN博客

LeetCode 热题 HOT 100(P21~P30)-CSDN博客

LeetCode 热题 HOT 100(P31~P40)-CSDN博客

LC020valid_parentheses

. - 力扣(LeetCode)

题目:

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

解法:

遇到右括号的时候需要判断有没有对应匹配的左括号,因为必须是类型相同的才能进行闭合,因此需要一个栈来维护遇到的左括号,看栈顶的左括号是否跟当前的右括号匹配。

 static Map<Character, Character> cache = new HashMap<>();

    static {
        cache.put(')', '(');
        cache.put('}', '{');
        cache.put(']', '[');
    }

    public boolean isValid(String s) {
        LinkedList<Character> stack = new LinkedList<>();
        for (char c : s.toCharArray()) {
            //  遇到右括号就需要一定要有匹配的左括号
            if (cache.containsKey(c)) {
                if (stack.isEmpty()) {
                    return false;
                }
                if (cache.get(c) != stack.pop()) {
                    return false;
                }

            } else {
                //左括号的情况,直接入栈
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }

这里有个技巧,维护一个右括号为key 的map,这样方便判断和匹配。

LC021merge_two 

. - 力扣(LeetCode)

题目:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

解法:

遇到链表题,直接创建一个pre 节点,接着轮流比较两边的大小,比较繁琐的是需要判断很多临界情况。

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode pre = new ListNode();
        ListNode cur = pre;
        while (list1 != null || list2 != null) {
            ListNode next;
            if (list1 == null) {
                next = list2;
                list2 = list2.next;
            } else if (list2 == null) {
                next = list1;
                list1 = list1.next;
            } else {
                if (list1.val < list2.val) {
                    next = list1;
                    list1 = list1.next;
                } else {
                    next = list2;
                    list2 = list2.next;
                }
            }
            cur.next = next;
            cur = cur.next;
        }

        return pre.next;
    }

LC022generate_parentheses

. - 力扣(LeetCode)

题目:

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

题解:

一般这种题都是递归的思路,相当于有2n个位置,每个位置都可能至多有2种策略,不是放左括号就是放右括号,放好之后再接着放下一个。这里有个有效括号的限制,所以想清楚这个就相对容易了。

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

    public List<String> generateParenthesis(int n) {
        dsf("", n, n);
        return result;
    }

    /**
     * 递归的写法
     *
     * @param path  当前的字符串
     * @param left  左括号剩余使用次数
     * @param right 右括号剩余使用次数
     */
    private void dsf(String path, int left, int right) {
        // 错误的情况
        if (left > right) {
            return;
        }
        if (left == 0 && right == 0) {
            result.add(path);
            return;
        }
        if (left > 0) {
            dsf(path + "(", left - 1, right);
        }
        if (right > 0) {
            dsf(path + ")", left, right - 1);
        }

    }

这里使用剩余使用测试,而不是已经使用次数,会让代码更加清晰。

递归的写法在熟悉递归模板之后,就是对什么时候跳出递归的判断,这一步思考清楚,后面就不难了。

LC023merge_k

. - 力扣(LeetCode)

题目:

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

题解:

其实就是利用递归的思路,先对半往下递归,最后是两个链表的合并(LC021merge_two )。

public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }
        return merge(lists, 0, lists.length - 1);
    }

    private ListNode merge(ListNode[] lists, int left, int right) {
        if (left == right) {
            return lists[left];
        }
        int mid = left + (right - left) / 2;
        final ListNode l1 = merge(lists, left, mid);
        final ListNode l2 = merge(lists, mid + 1, right);
        return merge2(l1, l2);
    }

    private ListNode merge2(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val < l2.val) {
            l1.next = merge2(l1.next, l2);
            return l1;
        } else {
            l2.next = merge2(l1, l2.next);
            return l2;
        }
    }

递归真是好东西啊,任何复杂的问题都能化繁为简。

LC031next_permutation

. - 力扣(LeetCode)

题目:

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
给你一个整数数组 nums ,找出 nums 的下一个排列。

题解:

这类题目就是典型的套路题了,当初靠自己想破脑袋估计也想不出来,直接看题解知道套路,掌握思路后加上自己在事例上推演下也就能写出来了。

public void nextPermutation(int[] nums) {
        final int len = nums.length;
        if (len < 2) {
            return;
        }
        int i = len - 2, k = len - 1;
        //找到最右边的“小数”
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        //如果不是最后一种情况,注意这里包含=0 的情况
        if (i >= 0) {
            //从后往前,找到尽可能“小”的大数
            while (nums[i] >= nums[k]) {
                k--;
            }
            int tmp = nums[i];
            nums[i] = nums[k];
            nums[k] = tmp;
        }
        //需要将i+1 之后的数字排序
        //将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。
        Arrays.sort(nums, i + 1, len);
    }

LC032longest_valid

. - 力扣(LeetCode)

题目:

给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度

题解:

“最*** ” 的基本是动态规划的思路。只是这里情况比较复杂,需要考虑很多边界情况

public int longestValidParentheses(String s) {
        final char[] chars = s.toCharArray();
        int[] dp = new int[chars.length];
        int max = 0;
        for (int i = 1; i < chars.length; i++) {
            //只有封闭的右括号才有可能配对
            if (chars[i] == ')') {
                //跟前一位刚好匹配
                if (chars[i - 1] == '(') {
                    // 数量就是前前位+2,这里>= 还是> 实际是一样的,因为dp[0]=0
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else { // i-1 也是右括号
                    final int pre = i - 1 - dp[i - 1];
                    if (pre >= 0 && chars[pre] == '(') {
                        dp[i] = (pre > 0 ? dp[pre - 1] : 0) + dp[i - 1] + 2;
                    }
                }
                max = Math.max(max, dp[i]);
            }

        }
        return max;
    }

这个也可以算套路题吧,动态规划还是挺考验功力的,动态方程往往取决动态数组的定义。这类题只能靠多做,多培养感觉了。

LC033search_in

. - 力扣(LeetCode)

题目:

整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= 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 。

题解:

经典的二分查找啊,之前刚学二分查找的时候觉得这玩意还不简单,没有特意准备,结果是啪啪打脸了。先复习下二分查找模板:

left, right = 0, len(array) - 1 
/注意这里的=
while left <= right:
	mid = left+(right - left) / 2 
    if array[mid] == target:
		# find the target!!
		break or return result 
    elif array[mid] < target:
		left = mid + 1 
	else:
		right = mid - 1

回到这道题,关键点是旋转数组始终有一半是有序的,这个时候针对有序的一边进行判断和处理,如果不在这一边就说明在另一边,说明当前这一边可以舍弃,继续下一步迭代。

public int search(int[] nums, int target) {
        if (nums.length == 0) {
            return -1;
        }
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            //左边有序的情况
            if (nums[left] <= nums[mid]) {
                if (target >= nums[left] && target <= nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else {
                if (target > nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }

LC34find_first

. - 力扣(LeetCode)

题目:

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

题解:

这道题比较有意思,一般我们通过折半查找都是确定具体的位置,这里是要找做边界和右边界,实际跟折半查找非常类似,只是左边界往小的那一边继续找,右边界就往大的那一边继续找。

public int[] searchRange(int[] nums, int target) {
        final int len = nums.length;
        if (len == 0) {
            return new int[]{ -1, -1 };
        }
        int left = 0, right = len - 1;
        int begin = -1, end = -1;
        //先找左边起始点
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //要找左边界,需要往尽可能小的方向寻找
            if (nums[mid] == target) {
                begin = mid;
                right = mid - 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        left = 0;
        right = len - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //要找右边界,需要往尽可能大的方向寻找
            if (nums[mid] == target) {
                end = mid;
                left = mid + 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return new int[]{ begin, end };
    }

可以看到有挺多重复代码,可以提一个方法出来

public int[] searchRange(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        int begin = getLocation(nums, target, left, right, true);
        int end = getLocation(nums, target, left, right, false);
        return new int[]{ begin, end };

    }

    /**
     * 找到target 对应的下标
     *
     * @param nums
     * @param target
     * @param left
     * @param right
     * @param isBegin 是否起始点
     * @return
     */
    private int getLocation(int[] nums, int target, int left, int right, boolean isBegin) {
        int location = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                location = mid;
                //关键点在于找起止点的时候对左右游标的处理
                if (isBegin) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else if (target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return location;
    }

LC39combination_sum

. - 力扣(LeetCode)

题目:

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
 candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

题解:

也是典型的递归思路,关键是怎么保证不走回头路,比如有整数数组是(1,2,3),在递归1 的时候,把所有可以跟1组合的所有数组都穷举了,那么递归到2 的时候,就不能再用1 了,因为这个组合肯定在之前递归1的时候已经存在了。

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if (candidates.length == 0) {
            return result;
        }
        dfs(candidates, new ArrayList<>(), 0, target);
        return result;
    }

    /**
     * 注意这里需要一个start 标识,用来避免走回头路引起的重复数据
     *
     * @param candidates
     * @param path
     * @param start
     * @param target
     */
    private void dfs(int[] candidates, ArrayList<Integer> path, int start, int target) {
        if (target == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (target < 0) {
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            path.add(candidates[i]);
            dfs(candidates, path, i, target - candidates[i]);
            path.remove(path.size() - 1);
        }

    }

LC46permutations

. - 力扣(LeetCode)

题目:

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

题解:

也是用递归解决,难点在于去重。这里通过一个boolean 数组来标识已经使用过的数字。这里可以复习下上一篇的递归模板。

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

    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length];
        dfs(nums, new ArrayList<>(), used);
        return result;

    }

    private void dfs(int[] nums, ArrayList<Integer> path, boolean[] used) {
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            dfs(nums, path, used);
            used[i] = false;
            path.remove(path.size() - 1);
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值