算法基础一

两数之和

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

解题思路:这道题最优的做法时间复杂度是O(n),顺序扫描数组,对每一个元素在map中找能组合给定值的另一半数字,如果找到了直接返回2个数字的下标。如果找不到就把数字放入map,进入下一次循环。


class Solution {

    public int[] twoSum(int[] nums, int target) {
        if (nums == null) {
            return null;
        }

        if (nums.length <= 1) {
            return null;
        }

        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int res = target - nums[i];
            if (map.get(res) != null) {
                return new int[]{map.get(res), i};
            }
            map.put(nums[i], i);
        }
        return null;
    }

}

两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。

解题思路:为了处理方法统一,可以先建立一个虚拟头节点,这个虚拟头结点的next指向真正的head,这样head不需要单独处理,直接while循环即可。另外判断循环终止的条件不是p.next !=null ,这样最后一位还需要额外计算,循环条件应该是p != null。


  public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        if (l1 == null || l2 == null) {
            return null;
        }

        // 进位的数,要么0要么1
        int carry = 0;
        ListNode res = new ListNode();
        ListNode pre = res;
        while(l1 != null ||l2 != null) {
            int v1 = l1 == null ? 0 : l1.val;
            int v2 = l2 == null ? 0 : l2.val;
            // 求和
            int sum = v1 + v2 + carry;
            // 计算进位
            carry = sum / 10;
            // 求和排除大于10的情况
            sum = sum % 10;

            res.next = new ListNode(sum);
            res = res.next;

            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }

        if (carry == 1) {
            // 进位了
            res.next = new ListNode(carry);
        }
        return pre.next;
    }

无重复字符的最长子串

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

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

解题思路: 思想是滑动窗口,滑动窗口的右边界不断地右移,只要没有重复的字符,就持续向右扩大窗口边界。一旦出现重复字符,就需要缩小左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度。

 public int lengthOfLongestSubstring(String s) {
        int len = s.length();

        Set<Character> temp = new HashSet<Character>();

        int rk = -1;
        int res = 0;

        for(int i = 0; i < len; ++i) {
            if (i != 0) {
                // 从第二个开始,移除前一个
                temp.remove(s.charAt(i - 1));
            }

            while(rk + 1 < len && !temp.contains(s.charAt(rk + 1))) {
           		// 持续向右移动
                temp.add(s.charAt(rk + 1));
                ++rk;
            }

            res = Math.max(res, rk - i + 1);

        }
        return res;
    }

求两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数 。算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

解题思路:

  • 给出两个有序数组,要求找出这两个数组合并以后的有序数组中的中位数。要求时间复杂度为 O(log (m+n))
  • 最容易想到的就是把两个数组合并,然后取出中位数。但是合并数组的复杂度是O(max(m+n)),所以可以考虑使用二分搜索
  • 由于要找到最终合并以后数组的中位数,两个数组的总⼤⼩也知道,所以中间这个位置也是知道的。只需要⼆分搜索⼀个数组中切分的位置,另⼀个数组中切分的位置也能得到。为了使得时间复 杂度最⼩,所以⼆分搜索两个数组中⻓度较⼩的那个数组
  • 关键的问题是如何切分两个数组。其实就是如何切分数组 1 。先随便⼆分产⽣⼀个 midA,切分的线何时算满⾜了中位数的条件呢?即,线左边的数都⼩于右边的数,即, nums1[midA-1] ≤ nums2[midB] && nums2[midB-1] ≤ nums1[midA] 。如果这些条件都不满 ⾜,切分线就需要调整。如果 nums1[midA] < nums2[midB-1] ,说明 midA 这条线划分出来左 边的数⼩了,切分线应该右移;如果 nums1[midA-1] > nums2[midB] ,说明 midA 这条线划分 出来左边的数⼤了,切分线应该左移。经过多次调整以后,切分线总能找到满⾜条件的解
  • 假设现在找到了切分的两条线了, 数组 1 在切分线两边的下标分别是 midA - 1 和 midA 。 数 组 2在切分线两边的下标分别是 midB - 1 和 midB 。最终合并成最终数组,如果数组⻓度是奇 数,那么中位数就是 max(nums1[midA-1], nums2[midB-1]) 。如果数组⻓度是偶数,那么中间 位置的两个数依次是: max(nums1[midA-1], nums2[midB-1]) 和 min(nums1[midA], nums2[midB]) ,那么中位数就是 (max(nums1[midA-1], nums2[midB-1]) + min(nums1[midA], nums2[midB])) / 2 。
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;

        int len = m + n;
        int left = -1;
        int right = -1;

        int aStart = 0;
        int bStart = 0;

        for(int i = 0; i <= len / 2; i++) {
            left = right;
            if (aStart < m && (bStart >= n || nums1[aStart] < nums2[bStart])) {
                right = nums1[aStart++];
            } else {
                right = nums2[bStart++];
            }
        }

        if ((len & 1 ) == 0) {
            return (left + right) / 2.0;
        } else {
            return right;
        }
    }

整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。

解题思路:只需要注意一点,反转以后的数字要求在 [−2^31, 2^31 − 1] 范围内,超过这个范围的数字都要输出0。


 public int reverse(int x) {
        int res = 0;
        while(x != 0) {
            //每次取末尾数字
            int tmp = x % 10;
            //判断是否 大于 最大32位整数
            if (res > 214748364 || (res == 214748364 && tmp > 7)) {
                return 0;
            }
            //判断是否 小于 最小32位整数
            if (res < -214748364 || (res == -214748364 && tmp < -8)) {
                return 0;
            }
            res = res * 10 + tmp;
            x /= 10;
        }
        return res;
    }

你可能感兴趣的:(算法,算法,数据结构,java,后端,面试,leetcode)