Java实现LeetCode 题号:321 - 330

90 阅读3分钟

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

321. 拼接最大数

给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。

求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。

说明: 请尽可能地优化你算法的时间和空间复杂度。

示例 1:

输入: nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 输出: [9, 8, 6, 5, 3] 示例 2:

输入: nums1 = [6, 7] nums2 = [6, 0, 4] k = 5 输出: [6, 7, 6, 0, 4] 示例 3:

输入: nums1 = [3, 9] nums2 = [8, 9] k = 3 输出: [9, 8, 9]

class Solution {
   /*
假设数组一为[3,4,6,5]、数组二为[9,1,2,5,8,3]、k = 5;
组合情况有0 + 5、1 + 4、2 + 3、3 + 2、4 + 1五种情况,就是从此五种情况取出组合最大的一种;
Math.max(0, k - n)表示若数组二的元素个数 >= k,则数组一的元素个数可以从0开始取,否则在数组二的大小基础上补.
*/
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
    int m = nums1.length, n = nums2.length;
    int[] res = new int[k];
    for (int i = Math.max(0, k - n); i <= k && i <= m; i++) {
        int[] arr = merge(maxArr(nums1, i), maxArr(nums2, k - i), k);
        if (gt(arr, 0, res, 0)) res = arr;
    }
    return res;
}

/*
假设选择了2 + 3的情况,分别从两个数组取出相应元素个数的最大组合,对数组一来说就是[6,5],对数组二来说是[9,8,3];
n - i : 当前数组中,当前下标到结尾还有多少个元素;
j : 当前数组中i之前有多少个数加入到最大组合中;
n - i + j > k <=> n - i - 1 + j >= k : 当前下标的元素大于最大组合的末尾元素,就需要弹出,弹出后的元素减少,故j--,
n - i(数组剩余元素) - 1(去掉最大组合末尾元素) + j(最大组合中剩余元素)时刻保持 >= k;
if j < k : 先将最大组合填满再进行比较替换操作
 */
private int[] maxArr(int[] nums, int k) {
    int n = nums.length;
    int[] res = new int[k];
    for (int i = 0, j = 0; i < n; i++) {
        while (n - i + j > k && j > 0 && nums[i] > res[j-1]) j--;
        if (j < k) res[j++] = nums[i];
    }
    return res;
}

/*
假设数组一最大组合为[6,5],数组二最大组合为[9,8,3],进行双指针排序,排序后为[9,8,6,5,3]
 */
private int[] merge(int[] nums1, int[] nums2, int k) {
    int[] res = new int[k];
    for (int i = 0, j = 0, r = 0; r < k; r++)
        res[r] = gt(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
    return res;
}

/*
比较两数组相应位置大小,相等就一直跳过,直到不相等就比较.
 */
private boolean gt(int[] nums1, int i, int[] nums2, int j) {
    while (i < nums1.length && j < nums2.length && nums1[i] == nums2[j]) {
        i++;
        j++;
    }
    return j == nums2.length || (i < nums1.length && nums1[i] > nums2[j]);
}
}

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11 输出: 3 解释: 11 = 5 + 5 + 1 示例 2:

输入: coins = [2], amount = 3 输出: -1 说明: 你可以认为每种硬币的数量是无限的。

class Solution {
   private int min = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        Arrays.sort(coins); // asc
        dfs(coins, coins.length - 1, amount, 0);
        return min == Integer.MAX_VALUE ? -1 : min;
    }

    private void dfs(int[] coins, int ci, int rest, int cnt) {
        if (ci < 0) return;
        for (int i = rest / coins[ci]; i >= 0; i--) {
            int currRest = rest - i * coins[ci], currCnt = cnt + i;
            if (currRest > 0 && currCnt + 1 < min) dfs(coins, ci - 1, currRest, currCnt);
            else {
                if (currRest == 0 && currCnt < min) min = currCnt;
                break;
            }
        }
    }
}

324. 摆动排序 II

给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。

示例 1:

输入: nums = [1, 5, 1, 1, 6, 4] 输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6] 示例 2:

输入: nums = [1, 3, 2, 2, 3, 1] 输出: 一个可能的答案是 [2, 3, 1, 3, 1, 2] 说明: 你可以假设所有输入都会得到有效的结果。

进阶: 你能用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现吗?

    使用计数排序的方法,把数组内的值都记录下来,
    摆动序列比较的是相对的值,所有值同时加减是无所谓的
    从右向左走一遍,然后给数组放入值,放入后,数组统计后的数量相对的值-1
    然后在从左向右走一遍,和上面是一样的 意思
class Solution {
       public void wiggleSort(int[] nums) {
        int max = Integer.MIN_VALUE;
        for (int num : nums) {
            max = Math.max(num, max);
        }
        int[] tmp = new int[max + 2];
        for (int num : nums) {
            tmp[num]++;
        }
        int a = 0, b = 1, i;
        for (i = tmp.length - 1; i > 0; i--) {
            while (tmp[i] > 0 && b < nums.length) {
                nums[b] = i;
                b += 2;
                tmp[i]--;
            }
            if (b >= nums.length) {
                break;
            }
        }

        while (i >= 0) {
            while (tmp[i] > 0 && a < nums.length) {
                nums[a] = i;
                a += 2;
                tmp[i]--;
            }
            if (a >= nums.length) {
                break;
            }
            if (tmp[i] > 0) {
                for (; tmp[i] > 0 && a < nums.length; tmp[i]--) {
                    nums[a] = i;
                    a += 2;
                }

            }
            i--;
        }
    }
}

326. 3的幂

给定一个整数,写一个函数来判断它是否是 3 的幂次方。

示例 1:

输入: 27 输出: true 示例 2:

输入: 0 输出: false 示例 3:

输入: 9 输出: true 示例 4:

输入: 45 输出: false 进阶: 你能不使用循环或者递归来完成本题吗?

class Solution {
    public boolean isPowerOfThree(int n) {   
           if (n == 0) {
            return false;
        }
        while (n % 3 == 0) {
            n /= 3;
        }
        return n == 1;
    }
}

327. 区间和的个数

给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。 区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

说明: 最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。

示例:

输入: nums = [-2,5,-1], lower = -2, upper = 2, 输出: 3 解释: 3个区间分别是: [0,0], [2,2], [0,2],它们表示的和分别为: -2, -1, 2。

二分递归调用
class Solution {
   public int countRangeSum(int[] nums, long lower, long upper) {
        long sums[] = new long[nums.length];
        for (int i=0; i<nums.length; i++) {
            sums[i] = ((i-1 >= 0) ? sums[i-1] : 0) + nums[i];
        }
        //System.out.println(Arrays.toString(sums));
        int result = divideAndConquer(sums, 0, sums.length-1, upper, lower);

        return result;
    }

    private int divideAndConquer(long sums[], int start, int end, long upper, long lower) {
        if (start > end) return 0;
        if (start == end) return (sums[start] <= upper && sums[start] >= lower) ? 1 : 0;
        int mid = (start+end)/2;
        int counts = 0;
        counts += divideAndConquer(sums, start, mid, upper, lower);
        counts += divideAndConquer(sums, mid+1, end, upper, lower);

        int ls = start, le=mid;
        while (le >= start && sums[mid+1] - sums[le] <= upper) le--;
        for (int r=mid+1; r<=end; r++) {
            while (ls <= mid && sums[r] - sums[ls] >= lower) ls++;
            while (le+1 <= mid && sums[r] - sums[le+1] > upper ) le++;
            if (ls - le -1 < 0) continue;
            counts += (ls-le-1);
        }
        ls = start;
        int i = 0, r= mid+1;
        long merged[] = new long[end-start+1];
        while (ls <= mid || r <= end) {
            if (ls > mid || (r<=end && sums[r] < sums[ls])) {
                merged[i++] = sums[r++];
            } else {
                merged[i++] = sums[ls++];
            }
        }
        for (i=0; i<merged.length; i++) {
            sums[start+i] = merged[i];
        }
        //System.out.println(Arrays.toString(sums) + " "  + counts + "," + start + "-" + end);
        return counts;
    }
}

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL 输出: 1->3->5->2->4->NULL 示例 2:

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

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

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        // head 为奇链表头结点,o 为奇链表尾节点
        ListNode o = head;
        // p 为偶链表头结点
        ListNode p = head.next;
        // e 为偶链表尾节点
        ListNode e = p;
        while (o.next != null && e.next != null) {
            o.next = e.next;
            o = o.next;
            e.next = o.next;
            e = e.next;
        }
        o.next = p;
        return head;
    }
}

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

给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例 1:

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

输入: nums = [ [3,4,5], [3,2,6], [2,2,1] ] 输出: 4 解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

class Solution {
     public int longestIncreasingPath(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return 0;
        }
        int max = 0;
        int row = matrix.length;
        int col = matrix[0].length;
        int[][] dp = new int[row][col];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                max = Math.max(max, loop(matrix, Integer.MIN_VALUE, dp, i, j)); //从每个节点开始搜索
            }
        }
        return max;
    }
    /**
     * 记忆化搜索
     * @param mat  数据矩阵
     * @param pre  路径中的前一个结点数字
     * @param dp   保存从每个结点开始搜索的最长升序序列的长度
     * @param i    当前位置
     * @param j    当前位置
     * @return
     */
    private int loop(int[][] mat, int pre, int[][] dp, int i, int j) {
        if (i < 0 || j < 0 || i >= mat.length || j >= mat[0].length || mat[i][j] <= pre) {  //停止搜索条件
            return 0;
        }
        if (dp[i][j] != 0) {  //如果有数据,直接返回
            return dp[i][j];
        }
        int max = 0;  //进行搜索
        max = Math.max(max, loop(mat, mat[i][j], dp, i - 1, j));
        max = Math.max(max, loop(mat, mat[i][j], dp, i + 1, j));
        max = Math.max(max, loop(mat, mat[i][j], dp, i, j - 1));
        max = Math.max(max, loop(mat, mat[i][j], dp, i, j + 1));
        dp[i][j] = max + 1;
        return dp[i][j];
    }
}