LeetCode 100道题目和答案(面试必备)(二)

31.两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。
在这里插入图片描述

答案:

方法一:两个集合
计算两个数组的交集,直观的方法是遍历数组 nums1,对于其中的每个元素,遍历数组 nums2 判断该元素是否在数组 nums2 中,如果存在,则将该元素添加到返回值。假设数组 nums1 和 nums2 的长度分别是 mm 和 nn,则遍历数组 nums1 需要 O(m)O(m) 的时间,判断 nums1 中的每个元素是否在数组 nums2 中需要 O(n)O(n) 的时间,因此总时间复杂度是 O(mn)O(mn)。

如果使用哈希集合存储元素,则可以在 O(1)O(1) 的时间内判断一个元素是否在集合中,从而降低时间复杂度。

首先使用两个集合分别存储两个数组中的元素,然后遍历较小的集合,判断其中的每个元素是否在另一个集合中,如果元素也在另一个集合中,则将该元素添加到返回值。该方法的时间复杂度可以降低到 O(m+n)O(m+n)class Solution {
   
    public int[] intersection(int[] nums1, int[] nums2) {
   
        Set<Integer> set1 = new HashSet<Integer>();
        Set<Integer> set2 = new HashSet<Integer>();
        for (int num : nums1) {
   
            set1.add(num);
        }
        for (int num : nums2) {
   
            set2.add(num);
        }
        return getIntersection(set1, set2);
    }

    public int[] getIntersection(Set<Integer> set1, Set<Integer> set2) {
   
        if (set1.size() > set2.size()) {
   
            return getIntersection(set2, set1);
        }
        Set<Integer> intersectionSet = new HashSet<Integer>();
        for (int num : set1) {
   
            if (set2.contains(num)) {
   
                intersectionSet.add(num);
            }
        }
        int[] intersection = new int[intersectionSet.size()];
        int index = 0;
        for (int num : intersectionSet) {
   
            intersection[index++] = num;
        }
        return intersection;
    }
}

方法二:排序 + 双指针
如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。可以预见的是加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 pre 表示上一次加入答案数组的元素。

初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,且该数字不等于pre ,将该数字添加到答案并更新pre 变量,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。

class Solution {
   
    public int[] intersection(int[] nums1, int[] nums2) {
   
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int length1 = nums1.length, length2 = nums2.length;
        int[] intersection = new int[length1 + length2];
        int index = 0, index1 = 0, index2 = 0;
        while (index1 < length1 && index2 < length2) {
   
            int num1 = nums1[index1], num2 = nums2[index2];
            if (num1 == num2) {
   
                // 保证加入元素的唯一性
                if (index == 0 || num1 != intersection[index - 1]) {
   
                    intersection[index++] = num1;
                }
                index1++;
                index2++;
            } else if (num1 < num2) {
   
                index1++;
            } else {
   
                index2++;
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }
}
32.两个数组的交集II

给定两个数组,编写一个函数来计算它们的交集。

如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
在这里插入图片描述

答案:

方法一:哈希表
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。

首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。

为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
class Solution {
   
    public int[] intersect(int[] nums1, int[] nums2) {
   
        if (nums1.length > nums2.length) {
   
            return intersect(nums2, nums1);
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int num : nums1) {
   
            int count = map.getOrDefault(num, 0) + 1;
            map.put(num, count);
        }
        int[] intersection = new int[nums1.length];
        int index = 0;
        for (int num : nums2) {
   
            int count = map.getOrDefault(num, 0);
            if (count > 0) {
   
                intersection[index++] = num;
                count--;
                if (count > 0) {
   
                    map.put(num, count);
                } else {
   
                    map.remove(num);
                }
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }
}
33.单词规律

给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

在这里插入图片描述

答案:

方法一:哈希表
思路及解法

在本题中,我们需要判断字符与字符串之间是否恰好一一对应。即任意一个字符都对应着唯一的字符串,任意一个字符串也只被唯一的一个字符对应。在集合论中,这种关系被称为「双射」。

想要解决本题,我们可以利用哈希表记录每一个字符对应的字符串,以及每一个字符串对应的字符。然后我们枚举每一对字符与字符串的配对过程,不断更新哈希表,如果发生了冲突,则说明给定的输入不满足双射关系。

在实际代码中,我们枚举 \textit{
   pattern}pattern 中的每一个字符,利用双指针来均摊线性地找到该字符在 \textit{
   str}str 中对应的字符串。每次确定一个字符与字符串的组合,我们就检查是否出现冲突,最后我们再检查两字符串是否比较完毕即可。
class Solution {
   
    public boolean wordPattern(String pattern, String str) {
   
        Map<String, Character> str2ch = new HashMap<String, Character>();
        Map<Character, String> ch2str = new HashMap<Character, String>();
        int m = str.length();
        int i = 0;
        for (int p = 0; p < pattern.length(); ++p) {
   
            char ch = pattern.charAt(p);
            if (i >= m) {
   
                return false;
            }
            int j = i;
            while (j < m && str.charAt(j) != ' ') {
   
                j++;
            }
            String tmp = str.substring(i, j);
            if (str2ch.containsKey(tmp) && str2ch.get(tmp) != ch) {
   
                return false;
            }
            if (ch2str.containsKey(ch) && !tmp.equals(ch2str.get(ch))) {
   
                return false;
            }
            str2ch.put(tmp, ch);
            ch2str.put(ch, tmp);
            i = j + 1;
        }
        return i >= m;
    }
}
答案二:
class Solution {
   
    public boolean wordPattern(String pattern, String str) {
   
        String[] words = str.split(" ");
        //字符和单词是互相映射,数量必须相等
        if (words.length != pattern.length()) {
   
            return false;
        }
        Map<Object, Integer> map = new HashMap<>();
        for (Integer i = 0; i < words.length; i++) {
   
            /*
                如果key不存在,插入成功,返回null;如果key存在,返回之前对应的value。

                以pattern = "abba", str = "dog cat cat dog"为例,
                第1次:map.put('a',0)返回null,map.put("dog",0)返回null,两者相等;
                第2次:map.put('b',1)返回null,map.put("cat",1)返回null,两者相等;
                第3次:map.put('b',2)返回1,map.put("cat",2)返回1,两者相等;
                第4次:map.put('a',3)返回0,map.put("dog",3)返回0,两者相等,
                结果为 true。

                以pattern = "abba", str = "dog cat cat fish"为例,
                第1次:map.put('a',0)返回null,map.put("dog",0)返回null,两者相等;
                第2次:map.put('b',1)返回null,map.put("cat",1)返回null,两者相等;
                第3次:map.put('b',2)返回1,map.put("cat",2)返回1,两者相等;
                第4次:map.put('a',3)返回0,map.put("fish",3)返回null,两者不相等,
                结果为 false。
            */
            if (map.put(pattern.charAt(i), i) != map.put(words[i], i)) {
   
                return false;
            }
        }
        return true;
    }
}
34.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

在这里插入图片描述

答案:

方法一:双指针
思路及解法
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

注意到以下性质:
左指针左边均为非零数;
右指针左边直到左指针处均为零。
因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。
class Solution {
   
    public void moveZeroes(int[] nums) {
   
        int n = nums.length, left = 0, right = 0;
        while (right < n) {
   
            if (nums[right] != 0) {
   
                swap(nums, left, right);
                left++;
            }
            right++;
        }
    }

    public void swap(int[] nums, int left, int right) {
   
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}
35.猜数字大小

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。
在这里插入图片描述

答案:

方法一:二分查找
记选出的数字为 \textit{
   pick}pick,猜测的数字为 xx。根据题目描述,若 \texttt{
   guess}(x)\le 0guess(x)0 则说明 x\ge\textit{
   pick}x≥pick,否则 x<\textit{
   pick}x<pick。

根据这一性质我们可以使用二分查找来求出答案 \textit{
   pick}pick。

二分时,记当前区间为 [\textit{
   left},\textit{
   right}][left,right],初始时 \textit{
   left}=1left=1,\textit{
   right}=nright=n。记区间中间元素为 \textit{
   mid}mid,若有 \texttt{
   guess}(mid)\le 0guess(mid)0 则说明 \textit{
   pick} \in [\textit{
   left},\textit{
   mid}]pick∈[left,mid],否则 \textit{
   pick} \in [\textit{
   mid}+1,\textit{
   right}]pick∈[mid+1,right]。当区间左右端点相同时,则说明我们找到了答案,退出循环。
public class Solution extends GuessGame {
   
    public int guessNumber(int n) {
   
        int left = 1, right = n;
        while (left < right) {
    // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (guess(mid) <= 0) {
   
                right = mid; // 答案在区间 [left, mid] 中
            } else {
   
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }
}
36.比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

答案:

方法一:二分查找
public class Solution extends GuessGame {
   
    public int guessNumber(int n) {
   
        int left = 1, right = n;
        while (left < right) {
    // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (guess(mid) <= 0) {
   
                right = mid; // 答案在区间 [left, mid] 中
            } else {
   
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }
}
36.比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

在这里插入图片描述

答案:

在这里插入图片描述

方法一:Brian Kernighan 算法
最直观的做法是对从 0 到 n 的每个整数直接计算「一比特数」。每个int 型的数都可以用 32 位二进制数表示,只要遍历其二进制表示的每一位即可得到 1 的数目。

利用 Brian Kernighan 算法,可以在一定程度上进一步提升计算速度。Brian Kernighan 算法的原理是:对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。

对于给定的 n,计算从 0 到 n 的每个整数的「一比特数」的时间都不会超过 O(logn),因此总时间复杂度为O(nlogn)class Solution {
   
    public int[] countBits(int n) {
   
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
   
            bits[i] = countOnes(i);
        }
        return bits;
    }

    public int countOnes(int x) {
   
        int ones = 0;
        while (x > 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值