[LeetCode Hot 100]两数之和、加,无重复最长子串、正序数组中位数

1.两数之和

力扣第一题,题目描述不再贴上来了。关键是数组无序,方法返回值要求是两数的索引

常规解法:

双重循环

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for(int i = 0; i < n; i++){
        //序号比i小的元素均与当前i比对过,故只需向后看
           for(int j = i + 1; j < n;j++){
               if(nums[j] == target - nums[i]){
                   return new int[]{i,j};
               }
           }
        }
        return new int[0];
    }
}

HashMap

采用空间换时间的思路,建立值-索引的映射关系。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashTable = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            if(hashTable.containsKey(target - nums[i])){
                //值到索引的映射(题目返回的是索引值)
                return new int[]{hashTable.get(target - nums[i]),i};
            }
            hashTable.put(nums[i],i);
        }
        return new int[0];
    }
}

进阶解法

以下摘自alexhilton的题解

二分法

确定一个数的索引后,在后续的值利用双指针从两头向中间找来节省时间

    public int[] twoSum(int[] nums, int target) {
        int[] result = {0, 1};
        if (nums.length <= 2) {
            return result;
        }

        for (int i = 0; i < nums.length - 1; i++) {
            result[0] = i;//首个索引
            int x = target - nums[i];//当前目标值
            //j从i+1向后找,k从后向前找,直至与j相遇
            for (int j = i + 1, k = nums.length - 1; j <= k; j++, k--) {
                //任一指针找到目标值,即返回指针位置。
                if (nums[j] == x) {
                    result[1] = j;
                    return result;
                }
                if (nums[k] == x) {
                    result[1] = k;
                    return result;
                }
            }
        }
        return result;
    }

四分法

相比于二分法,将固定数,也改为二分法。

    public int[] twoSum(int[] nums, int target) {
        int[] result = {0, 1};
        if (nums.length <= 2) {
            return result;
        }

        //主循环
        for (int i = 0, j = nums.length - 1; i < j; i++, j--) {
            if (nums[i] + nums[j] == target) {
                // Lucky
                result[0] = i;
                result[1] = j;
                return result;
            }

            // should be in [i+1, j-1], find them with double pointers.
            int x = target - nums[i];
            int y = target - nums[j];
            for (int k = i + 1, m = j - 1; k <= m; k++, m--) {
                result[0] = i;
                if (nums[k] == x) {
                    result[1] = k;
                    return result;
                } else if (nums[m] == x) {
                    result[1] = m;
                    return result;
                }

                result[1] = j;
                if (nums[k] == y) {
                    result[0] = k;
                    return result;
                } else if (nums[m] == y) {
                    result[0] = m;
                    return result;
                }
            }
        }

        return result;
    }

时空复杂度分析

双重循环HashMap二分法四分法
时间复杂度O(n²)O(n)O(nlogn)O(2/nlogn)
空间复杂度O(1)O(n)O(1)O(1)

两数相加

链表逆序存储两数对应位,考察链表遍历 + 加法进位的处理。
技巧:dummy虚拟头结点的使用

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //创建和链表虚结点
        ListNode dummy = new ListNode(-1);
        //和链表的指针
        ListNode p = dummy;
        //两数链表的指针
        ListNode p1 = l1, p2 = l2;
        //记录进位
        int carry = 0;
        //两数位数不相同,高位补零对齐相加。
        while(p1 != null || p2 != null || carry != 0){
            //val记录当前位置的结果,进位 + 值
            int val = carry;
            if(p1 != null){
                val += p1.val;
                p1 = p1.next;
            }
            if(p2 != null){
                val += p2.val;
                p2 = p2.next;
            }
            carry = val/10;
            val = val % 10; //当前对应位和
            ListNode t = new ListNode(val);
            p.next = t;
            p = p.next;
        }
        
        return dummy.next;
    }
}

最长无重复子串

双指针 + 滑动窗口

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), res = 0;
        //记录字符最新位置
        Map<Character, Integer> map = new HashMap<>();
        int left = 0, right = 0;
        while(right < n){
            char cr = s.charAt(right);
            if(map.containsKey(cr)){
                //即前面已出现,左指针移动到最近相同位置后面
                left = Math.max(map.get(cr) + 1, left);
            }
            res = Math.max(res, right - left + 1);
            map.put(cr,right);
            right++;
        }
        
        return res;
    }
}

正序数组中位数

要求时间复杂度为O(log(m+n))
联想到二分法解题。

二分法(官方)

力扣官方

进阶解法:

假设中位数索引值为k
利用中位数特点与第k小结合;
在长度更短的数组里寻找最大的索引m(其下标不超过K),其小于更长数组k - m -1下标处的值。
m-1、k-m-1分别锁定了两个数组处于前K个最小数里的最大值的下标。
由中位数特点可知:
当两数组长度之和为奇数时,应在边界附近(此解法在边界左侧)
当两数组长度之和为偶数时,应取边界左侧(前K个数最大值)和右侧(两数组在前K个数里最大值下标+1后的最小值)的平均值。
时间复杂度:O(log min(m,n))

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        if(n>m){
            return findMedianSortedArrays(nums2, nums1);
        }
    
        int k = (n + m + 1)/2;//奇数中位数落在左边
        int left = 0;
        int right=n;

        //二分法寻找nums1中满足<nums[m2]的最大下标
        while(left < right){
            int m1 = left + (right - left) /2;
            int m2 = k - m1;
            if(nums1[m1] < nums2[m2 - 1]){
                left = m1 + 1;
            }
            else{
                right = m1;
            }
        }
        
        //m1-1是前K个最小数中nums1的最大下标,即最靠近中位数边界
        int m1 = left;
        int m2 = k - left;
        int c1 = Math.max(m1 <= 0 ? Integer.MIN_VALUE : nums1[m1-1],
                          m2 <= 0 ? Integer.MIN_VALUE : nums2[m2-1]);
        //奇数,中位数落在边界左边,必然是组成前K个里,nums1和nums2中下标更大的。
        if((n + m)%2 ==1) return c1;

        int c2 = Math.min(m1 >= n ? Integer.MAX_VALUE : nums1[m1],
                          m2 >= m ? Integer.MAX_VALUE : nums2[m2]);
        //偶数,边界平均值,中位数取前K个数的最大值和K+1的值(nums1/nums2中更小的数)的平均值
        return (c1 + c2) * 0.5;

    }   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值