LeetCode 热题 HOT 100(P1~P10)

 系列文章: 

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

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

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

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

🔥 LeetCode 热题 HOT 100

这里记录下刷题过程中的心得,算法题基本是个套路活,很多时候你不知道套路或者模板,第一次尝试去做的时候就会非常懵逼、沮丧和无助。而且就算你一时理解并掌握了,过一段时间往往会绝望地发现你又不会了。所以过遍数就非常重要,我目前配合 anki  来复习。

这里不得不吐槽一下LeetCode 题目的排序,他既不是按照题目的难度也不是按照普遍意义上算法学习的阶段来排的,导致可能在刚开始就遇上难到怀疑人生的题目。所以我建议是刚开始的时候战略放弃困难的题目,等后面逐步掌握套路之后再看这些题目会有思路的多。

 LC001two_sum 两数之和(简单)

. - 力扣(LeetCode)

题目:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

解法:

使用HashMap 是比较常规的解法,这里有个技巧是只通过一遍循环就能解决问题。从头开始遍历,然后顺便把元素放入HashMap,这样就不用先遍历一遍初始化HashMap。

public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> cache = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int value = target - nums[i];
            if (cache.containsKey(value)) {
                return new int[]{ i, cache.get(value) };
            }
            cache.put(nums[i], i);
        }
        return null;

    }

LC002add_two_numbers 两数相加(中等)

题目:

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

解法:

链表题通常的操作是先增加一个前置pre 节点,这样就能大大方便后续的操作。因为最后你要返回头节点,这个时候pre.next 就是。

这道题核心难点是对进位的处理,因此需要有一个变量表示每次的进位,同时在最后还要对进位进行判断,这点特别容易遗漏。剩下的就是一些细节的处理,有一些为null 的情况需要处理,这点也非常容易踩坑。

 public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode pre = new ListNode(0);
        ListNode cur = pre;
        //表示进位
        int carry = 0;
        while (l1 != null || l2 != null) {
            int left = l1 == null ? 0 : l1.val;
            int right = l2 == null ? 0 : l2.val;

            int sum = left + right + carry;
            carry = sum / 10;
            sum = sum % 10;
            cur.next = new ListNode(sum);
            cur = cur.next;

            l1 = l1 == null ? null : l1.next;
            l2 = l2 == null ? null : l2.next;
        }
        if (carry == 1) {
            cur.next = new ListNode(carry);
        }
        return pre.next;
    }

LC003longest_substring 无重复字符的最长子串 (中等)

. - 力扣(LeetCode)

题目:

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

解法:

典型的滑动窗口策略,窗口的左右下标刚开始都是0,首先需要一个缓存用于存放已经遍历过的字符。右下标开始遍历,并跟缓存中的字符比较,存在就说明遇到重复的了,这个时候左右下标构成的字符串就是非重复的字符串,然后当前元素进缓存,左下标往前。这里需要注意要设置一个起始点,在每次判断更新最大长度的时候需要记录下这个起始点下标,不然最后输出的时候没办法定位。

public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> cache = new HashMap<>();
        int left = 0, max = 0;
        for (int i = 0; i < s.length(); i++) {
            final char key = s.charAt(i);
            if (cache.containsKey(key)) {
                // 这里+1 说明遇到重复字母,left 标往前挪动了一位
                left = Math.max(left, cache.get(key) + 1);
            }
            cache.put(key, i);
            max = Math.max(max, i - left + 1);
        }
        return max;
    }

这里有个优化性能的小技巧,因为字符由由英文字母、数字、符号和空格组成,因此可以用asc2 表示,这样可以用int[128] 代替HashMap。

LC004median_of_two 寻找两个正序数组的中位数 (困难)

. - 力扣(LeetCode)

题目:

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

解法:

难点在于复杂度要求为log,这个典型折半查找的思路了。

根据中位数的定义,当 m+n是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。

剩下的问题就是怎么在两个数组中找第k个数,并且还要是log 的复杂度。这里直接给结论,先比较两个数组中k/2 位置的2个数,小的那一个所在数组前k/2 就可以舍弃了,同样的思路接着找剩下的k/2,当然这里有很多细节和边界需要考虑。

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int length1 = nums1.length, length2 = nums2.length;
        int totalLength = length1 + length2;
        //个数为奇数的情况
        if (totalLength % 2 == 1) {
            int midIndex = totalLength / 2;
            double median = getKthElement(nums1, nums2, midIndex + 1);
            return median;
        } else {
            //个数偶数的情况
            int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
            double median =
                    (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
            return median;
        }
    }

    /**
     * 注意这里的k是第k位,并不是下标k
     *
     * @param nums1
     * @param nums2
     * @param k
     * @return
     */
    public int getKthElement(int[] nums1, int[] nums2, int k) {
        /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
         * 这里的 "/" 表示整除
         * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
         * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
         * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
         * 这样 pivot 本身最大也只能是第 k-1 小的元素
         * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
         * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
         * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
         */

        int length1 = nums1.length, length2 = nums2.length;
        int index1 = 0, index2 = 0;

        while (true) {
            // 边界情况
            if (index1 == length1) {
                return nums2[index2 + k - 1];
            }
            if (index2 == length2) {
                return nums1[index1 + k - 1];
            }
            if (k == 1) {
                return Math.min(nums1[index1], nums2[index2]);
            }

            // 正常情况
            int half = k / 2;
            int newIndex1 = Math.min(index1 + half, length1) - 1;
            int newIndex2 = Math.min(index2 + half, length2) - 1;
            int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
            if (pivot1 <= pivot2) {
                k -= (newIndex1 - index1 + 1);
                index1 = newIndex1 + 1;
            } else {
                k -= (newIndex2 - index2 + 1);
                index2 = newIndex2 + 1;
            }
        }
    }

getKthElement 可以用递归的方法改写下,这样能精简不少。

LC005longest_palindromic 最长回文子串 (中等)

. - 力扣(LeetCode)

题目:

给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

解法:

这里用了动态规划,动态规划的难点在于动态方程的推导,动态方程很多时候又取决于动态数组的定义,只能说多接触不同类型的题目来增长这方面的经验。

针对这道题定义了boolean 类型的2维数组,dp[i][j] 表示s[i][j]是否为回文。

然后就是2层循环不断推进的过程。

public String longestPalindrome(String s) {
        final int len = s.length();
        if (len < 2) {
            return s;
        }
        int begin = 0, max = 1;

        //dp[i][j] 表示s[i][j]是否为回文
        boolean[][] dp = new boolean[len][len];
        final char[] charArray = s.toCharArray();
        for (int j = 0; j < len; j++) {
            for (int i = 0; i <= j; i++) {
                if (charArray[j] == charArray[i]) {
                    //只有3个的时候,只要两边相等就是回文
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        //大于3个的时候就要看进一步的情况
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                //如果是回文就看下是否当前最大
                if (dp[i][j] && j - i + 1 > max) {
                    begin = i;
                    max = j - i + 1;
                }
            }
        }
        return s.substring(begin, begin + max);
    }
理解了核心思想之后,其实就是一些套路了。

 

LC010regular_expression 正则表达式匹配 (困难)

. - 力扣(LeetCode)

题目:

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

解法:

动态规划的思路,难在动态数组的定义和动态方程的推导。

动态数组dp[i][j]表示 s的前i个字符跟p的前j个字符是否能够匹配,然后就是考虑最后一个字符的各种可能的情况。 这个最好画图或者参考官网得分最高的题解。

这类题目思路对了,代码能否正确的写出来,以及边界情况是是否考虑周全才是真正考验。

public boolean isMatch(String s, String p) {
        final int row = s.length();
        final int col = p.length();

        //dp[i][j]表示 s的前i个字符跟p的前j个字符是否能够匹配
        boolean[][] dp = new boolean[row + 1][col + 1];
        dp[0][0] = true;
        for (int i = 0; i <= row; i++) {
            for (int j = 1; j <= col; j++) {
                if (p.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 2]; //*匹配0次的情况,相当于* 和*之前的字符都删除的情况
                    if (matches(s, p, i, j - 1)) {
                        dp[i][j] = dp[i][j] || dp[i - 1][j]; //*匹配1次或多次的情况
                    }
                } else if (matches(s, p, i, j)) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }
        return dp[row][col];
    }

    /**
     * 判断 s[i] 跟p[j] 是否能匹配,不包含*
     *
     * @param s
     * @param p
     * @param i
     * @param j
     * @return
     */
    private boolean matches(String s, String p, int i, int j) {
        if (i == 0) { //s[i]为空,p[j]不为* 的情况下,不可能匹配
            return false;
        }
        if (p.charAt(j - 1) == '.') {
            return true;
        }
        return s.charAt(i - 1) == p.charAt(j - 1);
    }

LC011container_with 盛最多水的容器 (中等)

. - 力扣(LeetCode)

题目:

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。

解法:

套路题,用双指针左右夹逼法求解。一开始左右指针在两边头尾,这个时候宽度是最大的,高是两边最矮的那个,然后进一步从矮的那边往中间靠近。

public int maxArea(int[] height) {
        int left = 0, right = height.length - 1;
        int max = 0;

        while (left < right) {
            int high = Math.min(height[left], height[right]);
            int wide = right - left;
            max = Math.max(max, high * wide);
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return max;
    }

LC015_3sum 三数之和(中等)

. - 力扣(LeetCode)

题目:

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

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

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

解法:

使用左右夹逼法,需要先对数组进行排序,从左边开始遍历i 作为锚点,然后左节点从i+,右节点从len-1 开始往中间进行移动,每移动一次计算下是否满足要求。

还有个难点是不能重复,这就包括了锚点、左节点、右节点在移动的时候都要去重处理。

public List<List<Integer>> threeSum(int[] nums) {
        if (nums.length < 3) {
            return null;
        }
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();
        for (int i = 0; i < nums.length - 2; i++) {
            if (nums[i] > 0) {
                return result;
            }

            //如果跟前一位相同说明已经遍历过同样的数值,可以直接跳过
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                final int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    left = findNextLeft(nums, left, right);
                    right = findNextRight(nums, left, right);
                } else if (sum < 0) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        return result;
    }

    private int findNextLeft(int[] nums, int left, int right) {
        while (left < right && nums[left] == nums[left + 1]) {
            left++;
        }
        return left + 1;
    }

    private int findNextRight(int[] nums, int left, int right) {
        while (left < right && nums[right] == nums[right - 1]) {
            right--;
        }
        return right - 1;
    }

LC017letter_combinations 电话号码的字母组合 (中等)

. - 力扣(LeetCode)

题目:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

解法:

典型的递归思路,递归类的代码,熟记模板就行了

// 递归模板
public void recur(int level, int param) {

	// terminator ,递归终止条件
	if (level > MAX_LEVEL) {
		// process result
		return; 
	}

	// process current logic
	// 处理当前层逻辑
	process(level, param);

	// drill down 
	// 下探到下一层
	recur( level: level + 1, newParam);

	// restore current status
	// 清理当前层,重要,特别是一些循环处理的场景,需要对公共变量进行还原

}
特别是最后的清理需要引起重视,很多时候就是在这里无意间在了跟头。
static Map<Character, String> cache = new HashMap<>();

    static {
        cache.put('2', "abc");
        cache.put('3', "def");
        cache.put('4', "ghi");
        cache.put('5', "jkl");
        cache.put('6', "mno");
        cache.put('7', "pqrs");
        cache.put('8', "tuv");
        cache.put('9', "wxyz");
    }

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

    public List<String> letterCombinations(String digits) {
        if (digits == null || Objects.equals("", digits)) {
            return result;
        }
        dsf(0, new StringBuilder(), digits);
        return result;
    }

    /**
     * 这里使用StringBuilder 会提升非常多,但是需要注意清理数据
     *
     * @param level
     * @param path
     * @param digits
     */
    private void dsf(int level, StringBuilder path, String digits) {
        //递归终止条件
        if (level >= digits.length()) {
            result.add(path.toString());
            return;
        }
        final String letter = cache.get(digits.charAt(level));
        for (char c : letter.toCharArray()) {
            dsf(level + 1, path.append(c), digits);
            //注意这里需要清理数据,这个跟直接使用string 作为参数不同
            path.deleteCharAt(path.length() - 1);
        }
    }

LC019remove_nth 删除链表的倒数第 N 个结点 (中等)

. - 力扣(LeetCode)

题目:

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

解法:

链表题就像前面说的,先搞一个虚拟的pre 节点。这道题的难点在倒数第n个,怎么计算这个位置成为关键。这里通过左右2个指针,先让右指针往前跑n步,然后左右指针一起往前跑,这样当有指针跑完的时候,左指针刚好停在倒数n结点的前一位上。

public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode();
        pre.next = head;
        ListNode right = pre, left = pre;
        //右边界先跑出去n远的距离
        for (int i = 0; i < n; i++) {
            right = right.next;
        }
        while (right != null && right.next != null) {
            left = left.next;
            right = right.next;
        }
        left.next = left.next.next;
        return pre.next;

    }

代码不复杂,典型的套路题,知道套路之后其实不难。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值