力扣记录:Hot100(1)——1-19

本次题目

      • 1 两数之和
      • 2 两数相加
      • 3 无重复字符的最长子串
      • 4 寻找两个正序数组的中位数
      • 5 最长回文子串
      • 10 正则表达式匹配
      • 11 盛最多水的容器
      • 15 三数之和
      • 17 电话号码的字母组合
      • 19 删除链表的倒数第 N 个结点

1 两数之和

  • 之前做过,使用HashMap存储数组中的数及其下标,遍历查找哈希表中是否存在目标值减当前值,注意两数下标应不同。
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //使用HashMap存储数及下标
        Map<Integer, Integer> map = new HashMap<>();
        //遍历查找哈希表中是否存在目标值减当前值
        for(int i = 0; i < nums.length; i++){
            if(map.containsKey(target - nums[i])){
                return new int[]{i, map.get(target - nums[i])};
            }
            map.put(nums[i], i);
        }
        //没找到
        return new int[2];
    }
}

2 两数相加

  • 分别遍历两个链表得到长度,在较长的链表上直接进行修改,设置虚拟头节点最后返回,设置进位符号并在每次相加后对进位进行判断,设置前一个节点用于最后一位进位时新建节点。
    • 优化:可以不用分别遍历两个链表,直接选择一条链表进行修改,不够长则直接新建节点。
    • 时间复杂度O(m + n)(优化后为O(max(m, n)),空间复杂度O(1)
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //直接在l1上修改
        //先判断两个链表的长度
        int leng1 = 0;
        ListNode temp1 = l1;
        while(temp1 != null){
            leng1 += 1;
            temp1 = temp1.next;
        }
        int leng2 = 0;
        ListNode temp2 = l2;
        while(temp2 != null){
            leng2 += 1;
            temp2 = temp2.next;
        }
        //将l1设为较长的链表
        if(leng1 < leng2){
            ListNode temp = l1;
            l1 = l2;
            l2 = temp;
        }
        ListNode head = new ListNode(0, l1); //虚拟头节点
        ListNode pre = null;    //最后一位进位需要新建节点
        int c = 0;  //进位
        while(l1 != null){
            //相加
            if(l2 != null){
                l1.val += l2.val + c;
            }else{
                l1.val += c;
            }
            //判断是否进位
            if(l1.val > 9){
                c = 1;
                l1.val %= 10;
            }else{
                c = 0;
            }
            //下一节点
            pre = l1;
            l1 = l1.next;
            if(l2 != null) l2 = l2.next;
        }
        //最后一位进位
        if(c == 1){
            pre.next = new ListNode(1);
        }
        return head.next;
    }
    //优化
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //直接在l1上修改
        ListNode head = new ListNode(0, l1); //虚拟头节点
        ListNode pre = null;    //最后一位进位需要新建节点
        int c = 0;  //进位
        while(l1 != null || l2 != null){
            //相加,若节点为null则加数为0,否则为节点值
            int x = l1 == null ? 0 : l1.val;
            int y = l2 == null ? 0 : l2.val;
            int sum = x + y + c;
            c = sum / 10;   //判断是否进位
            if(l1 == null){
                pre.next = new ListNode(sum % 10, null);
            }else{
                l1.val = sum % 10;
            }
            //下一节点
            pre = l1 == null ? pre.next : l1;
            if(l1 != null) l1 = l1.next;
            if(l2 != null) l2 = l2.next;
        }
        //最后一位进位
        if(c == 1){
            pre.next = new ListNode(1);
        }
        return head.next;
    }
}

3 无重复字符的最长子串

  • 子串问题(连续),同剑指 Offer 48 最长不含重复字符的子字符串,双指针+哈希表,使用HashMap存储字符串中字符最近一次出现的位置(下标),定义左右指针计算子串长度,左指针指向子串左边界,右指针向前移动更新map、右边界和最大值,若元素已出现过则更新左指针位置。
    • 时间复杂度O(n),空间复杂度O(字符集大小,128)
class Solution {
    public int lengthOfLongestSubstring(String s) {
        //定义HashMap存储字符串中字符最近一次出现的位置(下标)
        Map<Character, Integer> map = new HashMap<>();
        //双指针
        int left = -1;
        int right = 0;
        int res = 0;
        while(right < s.length()){
            if(map.containsKey(s.charAt(right))){   //已经出现过
                left = Math.max(left, map.get(s.charAt(right))); //只能向前移动
            }
            res = Math.max(res, right - left);  //更新最大值
            map.put(s.charAt(right), right);    //更新map
            right++;    //右指针向前移动
        }
        return res;
    }
}

4 寻找两个正序数组的中位数

  • 计算两个数组总长度,判断中位数的位置(一个或两个),定义双指针分别遍历两个数组,当计数到达中位数位置时进行记录(较小的),每次根据做左右指针指向值大小进行判断移动左指针或右指针(移动较小的),最后返回中位数
    • 时间复杂度O(m + n),空间复杂度O(1)
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //计算数组总长度
        int m = nums1.length;
        int n = nums2.length;
        int sum = m + n;
        int mid1 = sum / 2;
        int mid2 = sum / 2;
        if(sum % 2 == 0){   //判断中位数为两个数还是一个数
            mid1--;
        }
        //双指针分别遍历左右数组
        int count = 0;  //计数
        int left = 0;
        int right = 0;
        int res = 0;
        while((left < m || right < n) && count <= mid2){
            //其中一个数字遍历完成则设为最大值
            int n1 = left < m ? nums1[left] : Integer.MAX_VALUE;
            int n2 = right < n ? nums2[right] : Integer.MAX_VALUE;
            if(count == mid1 || count == mid2){ //计数达到中位数时返回较小的那个
                res += n1 < n2 ? n1 : n2;
            }
            //移动左右指针
            if(n1 < n2){
                left++;
            }else{
                right++;
            }
            count++;
        }
        if(mid1 == mid2) return res / 1.0;
        return res / 2.0;
    }
}
  • 二分法,设k = (m + n) / 2,求第k小的数,可以两个数组都取k/2的位置(数组不够长则直接指向末尾),比较两个数组对应位置上值的大小,比较小的数组该位置左边可以舍弃,然后更新k -=舍弃个数,重复以上步骤,直到k = 1,此时求两个数组对应位置上较小值即为中位数
    • 时间复杂度O(log(m + n)),空间复杂度O(1)
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //计算数组总长度
        int m = nums1.length;
        int n = nums2.length;
        int sum = m + n;
        //中位数为第mid1小的数和第mid2小的数(下标+1)
        int mid1 = sum / 2;
        int mid2 = sum / 2 + 1;
        if(sum % 2 == 1){   //判断中位数为两个数还是一个数
            mid1++;
        }
        //递归二分
        if(mid1 == mid2) return getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid1) / 1.0;
        return (getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid1) + getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid2)) / 2.0;
    }
    //递归进行二分,输入两个数组及其左右区间(左闭右闭)和目标值(第mid小)
    private int getMedian(int[] nums1, int left1, int right1, int[] nums2, int left2, int right2, int mid){
        //终止条件
        int len1 = right1 - left1 + 1;
        int len2 = right2 - left2 + 1;
        //if(len1 > len2) return getMedian(nums2, left2, right2, nums1, left1, right2, mid);
        if(len1 == 0) return nums2[left2 + mid - 1];    //其中一个数组空了
        if(len2 == 0) return nums1[left1 + mid - 1];
        if(mid == 1) return Math.min(nums1[left1], nums2[left2]);   //返回较小值
        //两边数组定位到mid/2 - 1的位置(下标)
        //数组不够长则直接指向末尾
        int i = left1 + Math.min(mid / 2, len1) - 1;
        int j = left2 + Math.min(mid / 2, len2) - 1;
        //比较两个数组对应位置上值的大小,比较小的数组该位置左边可以舍弃
        if(nums1[i] > nums2[j]){
            return getMedian(nums1, left1, right1, nums2, j + 1, right2, mid - (j - left2 + 1));
        }else{
            return getMedian(nums1, i + 1, right1, nums2, left2, right2, mid - (i - left1 + 1));
        }
    }
}

5 最长回文子串

  • 动态规划DP,定义dp数组dp[i] [j]表示下标i和下标j之间(包括i,j)是否为回文子串,是为true,否为false。从后向前遍历字符串,从前向后判断是否回文子串,若是则判断是否更新最大长度
    • 同样可计算回文子串个数
    • 时间复杂度O(n ^ 2),空间复杂度O(n ^ 2)
class Solution {
    public String longestPalindrome(String s) {
        int leng = s.length();
        if(leng == 1) return s;
        //动态规划DP
        //定义dp数组dp[i] [j]表示下标i和下标j之间(包括i,j)是否为回文子串
        //是为true,否为false
        boolean[][] dp = new boolean[leng][leng];
        int left = 0;   //存储最长回文子串的下标范围
        int right = 0;
        //从后向前遍历字符串,从前向后判断是否回文子串,若是则判断是否更新最大长度
        for(int i = leng - 1; i >= 0; i--){
            for(int j = i; j < leng; j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j - i <= 1){
                        dp[i][j] = true;    //长度小于1则直接为true("a""aa")
                    }else{
                        dp[i][j] = dp[i + 1][j - 1];    //否则判断中间是否回文子串
                    }
                }
                if(dp[i][j] == true && right - left < j - i){
                    left = i;
                    right = j;
                }
            }
        }
        return s.substring(left, right + 1);
    }
}
  • 中心扩散法,双指针,以一个元素或两个元素为中心向两边扩散直到字符串边界,求得该回文中心的最长回文子串长度,最后得到总体的最长回文子串长度

    • 同样可计算回文子串个数

    • 时间复杂度O(n ^ 2),空间复杂度O(1)

class Solution {
    int left;
    int right;
    public String longestPalindrome(String s) {
        int leng = s.length();
        if(leng == 1) return s;
        //中心扩散法
        left = 0;
        right = 0;
        //以一个元素或两个元素为中心向两边扩散直到字符串边界
        for(int i = 0; i < s.length(); i++){
            centerSpread(s, i, i);
            centerSpread(s, i, i + 1);
        }
        return s.substring(left, right + 1);
    }
    private void centerSpread(String s, int i, int j){
        //双指针
        //求得该回文中心的最长回文子串长度,最后得到总体的最长回文子串长度
        while(i >=0 && j < s.length() && s.charAt(i) == s.charAt(j)){
            if(right - left < j - i){
                right = j;
                left = i;
            }
            //向左右两边扩散
            i--;
            j++;
        }
    }
}

10 正则表达式匹配

  • 动态规划,参考剑指 Offer 19 正则表达式匹配,定义dp数组dp[i] [j]表示下标i - 1(包括i - 1)之前的字符串s子串和下标j - 1(包括j - 1)之前的字符串p子串是否匹配
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public boolean isMatch(String s, String p) {
        //动态规划
        int leng1 = s.length();
        int leng2 = p.length();
        //定义dp数组dp[i] [j]表示下标i - 1(包括i - 1)之前的字符串s子串
        //和下标j - 1(包括j - 1)之前的字符串p子串是否匹配
        boolean[][] dp = new boolean[leng1 + 1][leng2 + 1];
        //初始化,dp[i][0] = false但dp[0][j]可能为true
        dp[0][0] = true;
        for(int i = 0; i <= leng1; i++){
            for(int j = 1; j <= leng2; j++){
                if(p.charAt(j - 1) == '*'){//当j-1指向*时,此时j一定大于等于2,'*'前一定有字符
                    //s[i]和p[j - 1]不匹配
                    dp[i][j] = dp[i][j - 2];
                    //s[i]和p[j - 1]不匹配
                    if(i > 0 && (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.')){
                        dp[i][j] = dp[i][j] || dp[i - 1][j];
                    }
                }else{//当j-1指向字母或.时
                    //s[i]和p[j]匹配
                    if(i > 0 && (p.charAt(j - 1) == s.charAt(i - 1) || p.charAt(j - 1) == '.')){
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                }
            }
        }
        return dp[leng1][leng2];
    }
}

11 盛最多水的容器

  • 双指针,左右指针从数组两边出发向中间移动,每次计算水量(高*宽)后移动较小的指针
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public int maxArea(int[] height) {
        //双指针,左右指针从数组两边出发向中间移动
        int left = 0;
        int right = height.length - 1;
        int max = 0;
        while(left < right){
            //每次计算水量后移动较小的指针
            int h = Math.min(height[right], height[left]);
            int w = right - left;
            int sum = h * w;
            max = Math.max(max, sum);
            if(h == height[right]){
                right--;
            }else{
                left++;
            }
        }
        return max;
    }
}

15 三数之和

  • 双指针,之前做过,数组中可能有重复元素,哈希表去重比较麻烦,首先将数组升序排序,使用i遍历数组,左指针指向i+1,右指针指向数组最右端,如果三数和大于0则右指针向左移动,小于0则左指针向右移动,等于0则存储三个值(求和前去重),直到左右指针相遇i向前移动。
    • 如果i指向的数大于0可以直接返回结果,在求和前判断当前i,left,right和上一个i,left,right是否相同,相同的话得到的三数一致,跳过该数,可以使用Arrays.asList()直接新建List
    • 时间复杂度O(n ^ 2),空间复杂度O(logn)
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //双指针,数组中可能有重复元素,哈希表去重比较麻烦
        List<List<Integer>> result = new ArrayList<>();
        int leng = nums.length;
        if(leng < 3) return result;
        //首先将数组升序排序,使用i遍历数组,左指针指向i+1,右指针指向数组最右端
        Arrays.sort(nums);
        int left = 0;
        int right = leng - 1;
        for(int i = 0; i < leng; i++){
            //如果i指向的数大于0可以直接返回结果
            if(nums[i] > 0) break;
            //在求和前判断当前i和上一个i是否相同,相同的话得到的三数一致,跳过该数
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            left = i + 1;
            right = leng - 1;
            //直到左右指针相遇i向前移动
            while(left < right){
                if(nums[i] + nums[left] + nums[right] > 0){//如果三数和大于0则右指针向左移动
                    right--;
                    //判断当前right和上一个right是否相同,相同的话得到的三数一致,跳过该数
                    while(left < right && nums[right] == nums[right + 1]){
                        right--;
                    }
                }else if(nums[i] + nums[left] + nums[right] < 0){//小于0则左指针向右移动
                    left++;
                    //判断当前left和上一个left是否相同,相同的话得到的三数一致,跳过该数
                    while(left < right && nums[left] == nums[left - 1]){
                        left++;
                    }
                }else{//等于0则存储三个值(求和前去重)
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    //去重,注意这里左右指针判断方向不同
                    while(left < right && nums[right] == nums[right - 1]){
                        right--;
                    }
                    while(left < right && nums[left] == nums[left + 1]){
                        left++;
                    }
                    //下一轮
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
}

17 电话号码的字母组合

  • 回溯,之前做过,组合问题,先定义数字-字母对应数组,
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    List<String> result = new ArrayList<>();
    StringBuilder sb = new StringBuilder();
    public List<String> letterCombinations(String digits) {
        //回溯
        if(digits.length() == 0) return result;
        //定义数字-字母对应数组(0——9)
        String[] num = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        backtracking(digits, 0, num);
        return result;
    }
    //回溯函数,输入字符串,解析位置,数字-字母对应数组
    private void backtracking(String digits, int start, String[] num){
        if(start >= digits.length()){
            result.add(sb.toString());
            return;
        }
        String s = num[digits.charAt(start) - '0'];	//当前位对应的字符串
        for(int i = 0; i < s.length(); i++){	//遍历当前位对应字符串
            sb.append(s.charAt(i));
            backtracking(digits, start + 1, num);
            sb.deleteCharAt(sb.length() - 1);    //回溯
        }
    }
}

19 删除链表的倒数第 N 个结点

  • 双指针,之前做过,左右指针同时从链表头部出发,右指针先移动n次,然后左右指针同时移动直到右指针到链表尾部,此时左指针指向倒数第n个节点。同时定义左指针前一个节点用于删除操作,也可以直接定位到倒数第n-1个节点直接删除下一个节点。
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //双指针
        ListNode virtualHead = new ListNode(0, head);   //虚拟头节点
        ListNode pre = virtualHead; //要删除的前一个节点
        ListNode left = virtualHead.next;   //要删除的节点
        ListNode right = virtualHead.next;  //指向null时左指针为倒数第n个节点
        while(n > 0){   //右指针先移动n次
            right = right.next;
            n--;
        }
        while(right != null){//左右指针同时移动
            pre = left;
            left = left.next;
            right = right.next;
        }
        //删除left指向节点
        pre.next = pre.next.next;
        return virtualHead.next;
    }
}

你可能感兴趣的:(Hot100,LeetCode,leetcode,算法,数据结构)