LeetCode面试热题十三

326. 3 的幂

给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x

示例 :

输入:n = 27
输出:true

进阶:你能不使用循环或者递归来完成本题吗?
提示:

  • -231 <= n <= 231 - 1

解题思路:打表,将所有整数范围内的3的幂,存放进哈希表中,直接判断。

class Solution {
    static HashSet<Integer> set;
    static {
        set = new HashSet<>();
        int n = 1;
        while(n > 0){
            set.add(n);
            n *= 3;
        } 
    }
    public boolean isPowerOfThree(int n) {
        return set.contains(n);
    }
}

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 :

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:

  • 应当保持奇数节点和偶数节点的相对顺序。
  • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解题思路:直观的解法。

public ListNode oddEvenList(ListNode head) {
    int k = 1;
    ListNode oneHead,twoHead;
    ListNode p1,p2;
    oneHead = new ListNode();
    twoHead = new ListNode();
    p1 = oneHead;
    p2 = twoHead;
    while(head != null){
        if(k % 2 ==1){
            // 奇数节点
            p1.next = head;
            p1 = p1.next;
        }else{
            // 偶数节点
            p2.next = head;
            p2 = p2.next;
        }
        k++;
        head = head.next;
    }
    p1.next = null;
    p2.next = null;
    p1.next = twoHead.next;
    return oneHead.next;
}

329. 矩阵中的最长递增路径

给定一个 m x n 整数矩阵 matrix ,找出其中最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线 方向上移动或移动到边界外(即不允许环绕)。

示例:
请添加图片描述

输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4 
解释:最长递增路径为 [1, 2, 6, 9]

解题思路:DFS+记忆化搜索,对于每一个点进行深度优先遍历,寻找最长的递增路径长度,同时将已经找到的对于每一个点的最长递增路径长度保存下来。避免重复计算。

public int longestIncreasingPath(int[][] matrix) {
    int m = matrix.length;
    int n = matrix[0].length;
    // 记录最大长度
    int max = Integer.MIN_VALUE;
    // 记录以每一个点为起始位置的长度
    int[][] maxLen = new int[m][n];
    // 遍历每一个点,找到以当前点为起始位置的最大递增路径的长度
    for(int i = 0;i < m;i++){
        for(int j = 0;j < n;j++){
        	// 如果该点没有计算过,进行dfs计算
            if(maxLen[i][j] == 0){
                dfs(matrix,maxLen,i,j);
            }
            // 比较历史最大长度与当前点的长度,选择最大的长度
            max = Math.max(maxLen[i][j],max);
        }
    }
    return max;
} 
// 方法功能,找到matrix[i][j]的最大长度。
// 参数说明,matrix矩阵,maxLen记录每一个点的单元格为起始的最大长度,i,j当前位置
int[][] dir = {{0, 1},{1, 0},{0, -1},{-1, 0}};
public int dfs(int[][] matrix,int[][] maxLen,int i,int j){
	// 初始化为 1     
    maxLen[i][j] = 1;
    for(int di = 0;di < 4;di++) {
        // 向四周扩散寻找增长路径,保证在矩阵内,并且大于当前点
        int newi = dir[di][0] + i;
        int newj = dir[di][1] + j;
        if (0 <= newi && newi < matrix.length &&
                0 <= newj && newj < matrix[0].length &&
                matrix[newi][newj] > matrix[i][j]) {
            // 该点已经计算过
            if (maxLen[newi][newj] != 0) {
            	// 可以扩展的点长度+1,加上当前的点,与当前点大小比较,选择最大的值
                maxLen[i][j] = Math.max(maxLen[newi][newj] + 1,maxLen[i][j]);
            } else {
            	// 要扩展的点,没有计算过,进行dfs,计算结果+1,与当前点的值比较,选择最大的值
                maxLen[i][j] = Math.max(dfs(matrix, maxLen, newi,newj) + 1,maxLen[i][j]);
            }
        }
    }
    // 返回当前点的最大长度
    return maxLen[i][j];
}

334. 递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

方法一:动态规划(超时了
解题思路:动态规划,dp[i] 表示,当前递增子序列的长度。
状态转移方程:dp[i] = max(dp [ k ] + 1,dp[i]),如果 nums[i] > nums[k],(0 <= k < i)

public boolean increasingTriplet(int[] nums) {
    int len = nums.length;
    int[] dp = new int[len];
    for(int i = 0;i < len;i++){
        dp[i] = 1;
        for(int j = 0;j < i;j++){
            if(nums[i] > nums[j]){
                dp[i] = Math.max(dp[j] + 1,dp[i]);
                if(dp[i] == 3){
                    return true;
                }
            }
        }
    }
    return false;
}

方法二:维护两个值,出现的最小值和出现的最大值,如果数组中又出现了一个比最大值还大的值,正好构成递增的三元子序列。

public boolean increasingTriplet(int[] nums) {
    int len = nums.length;
    int min = Integer.MAX_VALUE,max = Integer.MAX_VALUE;
    for(int i = 0;i < len;i++){
        if(nums[i] <= min){
        	// 维护最小值
            min = nums[i];
        }else if(nums[i] < max){
        	// 维护最大值
            max = nums[i];
        }else if(nums[i] > max){
        	// 出现比最大值更大的数,构成三元子序列
            return true;
        }
    }
    return false;
}

341. 扁平化嵌套列表迭代器

给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。

实现扁平迭代器类 NestedIterator :

  • NestedIterator(List nestedList) 用嵌套列表 nestedList 初始化迭代器。
  • int next() 返回嵌套列表的下一个整数。
  • boolean hasNext() 如果仍然存在待迭代的整数,返回 true ;否则,返回 false 。

你的代码将会用下述伪代码检测:

initialize iterator with nestedList
res = []
while iterator.hasNext()
    append iterator.next() to the end of res
return res

示例:

输入:nestedList = [[1,1],2,[1,1]]
输出:[1,1,2,1,1]
解释:通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是:[1,1,2,1,1]

提示:

  • 1 <= nestedList.length <= 500
  • 嵌套列表中的整数值在范围 [-106, 106] 内

解题思路:DFS,将列表中的所有值放入 list集合中。再使用原生的迭代器完成功能即可。

public class NestedIterator implements Iterator<Integer> {
    List<Integer> list = new LinkedList<>();
    Iterator<Integer> iterator;
    public NestedIterator(List<NestedInteger> nestedList) {
    	// dfs将列表中所有的值放入 list集合
        dfs(nestedList);
        // 使用list集合得到迭代器344. 反转字符串
        iterator = list.iterator();
    }
    private void dfs(List<NestedInteger> nestedList){
        int len = nestedList.size();
        for(int i = 0;i < len;i++){
            if(nestedList.get(i).isInteger()){
                // 是单个整数
                list.add(nestedList.get(i).getInteger());
            }else{
                // 是列表
                dfs(nestedList.get(i).getList());
            }
        }
    }
    @Override
    public Integer next() {
        return iterator.next();
    }
    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }
}

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

提示:

  • 1 <= s.length <= 105
  • s[i] 都是 ASCII 码表中的可打印字符

解题思路:前后元素互换即可

public void reverseString(char[] s) {
    int i = 0;
    int j = s.length - 1;
    char c;
    while(i < j){
        c = s[i];
        s[i] = s[j];
        s[j] = c; 
        i++;
        j--;
    }
}

347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 :

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

解题思路:使用 hashMap 统计各个元素出现的次数,然后对出现次数进行排序,排序后前k个元素就是要找的结果。

public int[] topKFrequent(int[] nums, int k) {
    HashMap<Integer,Integer> map = new HashMap<>();
    // 统计出现次数
    for(int i = 0; i<nums.length;i++){
        map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
    }
    int[][] list = new int[map.size()][2];
    int index = 0;
    // 将map中的键值赋值给数组
    for(Map.Entry entry:map.entrySet()){
        list[index][0] = (int)entry.getKey();
        list[index][1] = (int)entry.getValue();
        index++;
    }
    // 按次数对数组进行排序
    Arrays.sort(list, (a, b) -> b[1]-a[1]);
    int[] ans = new int[k];
    // 将前k个赋值给ans
    for(int i = 0;i<k;i++){
        ans[i] = list[i][0];
    }
    // 返回最终结果
    return ans;
}

350. 两个数组的交集 II

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 :

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

方法一:排序+双指针
解题思路:对两个数组进行排序,然后从头开始遍历,比较,将相等的加入结果集。

public int[] intersect(int[] nums1, int[] nums2) {
	// 对数组进行排序
    Arrays.sort(nums1);
    Arrays.sort(nums2);
    List<Integer> list = new LinkedList<>();
    int i = 0;
    int j = 0;
    // 双指针
    while(i < nums1.length && j < nums2.length){
        if(nums1[i] < nums2[j]){
            i++;
        }else if(nums2[j] < nums1[i]){
            j++;
        }else{
            list.add(nums1[i]);
            i++;
            j++;
        }
    }
    // 把集合中的元素赋值给数组
    int[] ans = new int[list.size()];
    i = 0;
    for(int num : list){
        ans[i++] = num;
    }
    return ans;
}

方法二:哈希表
解题思路:把较短的数组中的所有元素和出现的次数,存放入哈希表中。然后使用较长的数组的所有元素与表中的元素和初现次数做比较。

public int[] intersect(int[] nums1, int[] nums2) {
    HashMap<Integer,Integer> hashMap = new HashMap<>();
    // 使得nums1数组的长度最小
    if(nums1.length > nums2.length){
        int[] tmp = nums1;
        nums1 = nums2;
        nums2 = tmp;
    }
    // nums1数组元素放入哈希表
    for(int i = 0;i<nums1.length;i++){
        hashMap.put(nums1[i],hashMap.getOrDefault(nums1[i],0) + 1);
    }
    List<Integer> list = new LinkedList<>();
    // nums2元素在哈希表寻找
    for(int i = 0;i<nums2.length;i++){
    	// 在哈希表中存在,说明该元素在交集中。
        if(hashMap.containsKey(nums2[i])){
        	// 将哈希表中次数减1
            int k = hashMap.get(nums2[i]);
            k--;
            if(k == 0){
            	// 次数为0,移除
                hashMap.remove(nums2[i]);
            }else{
                hashMap.put(nums2[i],k);
            }
            // 将元素加入集合中
            list.add(nums2[i]);
        }
    }
    // 将集合中的结果放入
    int[] ans = new int[list.size()];
    int i = 0;
    for(int num:list){
        ans[i++] = num;
    }
    return ans;
}

371. 两整数之和

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。
示例:

输入:a = 5, b = 6
输出:11

解题思路:使用位运算。
在二进制中:0 + 0 = 0,0 + 1 = 1,1 + 0 = 1,1 + 1 = 1(产生进位)
使用异或运算恰好能完成二进制的加法。只是丢失了进位。
使用与运算,1&1 = 1,使得结果左移一位,恰好就是进位值。
示例:
a = 5,b=6。二进制为 a = 0101,b = 0110
a+b = 11,二进制为 1011。
如:a ^ b = 0011,a & b << 1 = 1000(左移一位),a ^ b + a &b << 1 就是a+b的结果。
就把 a + b的运算转换为 a^b + a&b << 1 的结果。进行循环,每次对 a&b的结果左移一位,最终 a&b<<1必然为0。
a = 0011,b=1000
a ^ b = 1011
a & b << 1 = 0。
此时a就是正确结果,计算完成。

public int getSum(int a, int b) {
    while (b != 0) {
        int carry = (a & b) << 1;
        a = a ^ b;
        b = carry;
    }
    return a;
}

378. 有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。

示例:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

提示:

  • n == matrix.length
  • n == matrix[i].length
  • 1 <= n <= 300
  • -109 <= matrix[i][j] <= 109
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n2

方法一:归并排序
解题思路:把矩阵看成对 n个长度为n的有序数组的归并排序,排序到第k个小的元素为止。

public int kthSmallest(int[][] matrix, int k) {
    int n = matrix.length;
    // 使用最小堆,堆的根节点是最小值
    PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a,b)->(a[0]-b[0]));
    // 将所以数组的第一个元素加入堆中
    for(int i = 0;i<n;i++){
    	// 数组的第一个值为 矩阵中节点的值,第二个,三个表示所在矩阵的位置
        priorityQueue.offer(new int[]{matrix[i][0],i,0});
    }
    for(int i = 0;i < k-1;i++){
    	// 抛出最小的元素
        int[] min = priorityQueue.poll();
        if(min[2] + 1 < n){
        	// 把最小的元素的后一个元素加入堆
            priorityQueue.offer(new int[]{matrix[min[1]][min[2] + 1],min[1],min[2] + 1});
        }
    }
    return priorityQueue.peek()[0];
}

方法二:二分查找
解题思路:在matrix中,matrix[0][0] 是最小值,matrix[n-1][n-1]是最大值。第k小元素必定在[matrix[0][0], matrix[n-1][n-1]]区间内。
在 [matrix[0][0], matrix[n-1][n-1]] 区间中去猜第k小元素是哪一个。猜!!!使用二分查找加快猜对的速度。
初始化 left = matrix[0][0],right = matrix[n-1][n-1]。每次取 left和right的中间值去猜。
mid = left+(right-left)/ 2;
在矩阵中统计出 小于等于 mid 的个数nums,nums与 k 进行比较,如果 nums > k,那么说明猜的数mid过大,第k个最小值应该在 left到mid-1的区间中。如果 nums < k,说明猜的数mid过小,第k个最小的值应该在mid + 1到right的区间中。继续猜,直到 left = right,说明left就是第 k 个最小的数。

public int kthSmallest(int[][] matrix, int k) {
    int n = matrix.length;
    int left = matrix[0][0];
    int right = matrix[n - 1][n - 1];
    while (left < right) {
    	// 从left,right 区间中选择中间值去猜
        int mid = left + ((right - left) >> 1);
        if (check(matrix, mid, k, n)) {
        	// 小于等于mid的数量大于等于k,猜的数太大
            right = mid;
        } else {
        	// 小于等于mid的数量小于k,猜的数太小
            left = mid + 1;
        }
    }
    return left;
}
// 统计比小于等于mid的数量,并与k相比较
public boolean check(int[][] matrix, int mid, int k, int n) {
    int i = n - 1;
    int j = 0;
    int num = 0;
    // 从右下角开始统计
    while (i >= 0 && j < n) {
        if (matrix[i][j] <= mid) {
        	// 每次+i+1,为 matrix[0] 到 matrix[i]中第j列的数量
            num += i + 1;
            // 向右继续比较
            j++;
        } else {
        	// 向上继续比较
            i--;
        }
    }
    return num >= k;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值