Loading

🔥 LeetCode 热题 HOT 100(71-80)

253. 会议室 II(NO)

279. 完全平方数

class Solution {
    public int numSquares(int n) {
        // dp[i] : 组成和为 i 的最少完全平方数个数
        // base case: dp[0] = 0;
        int[] dp = new int[n + 1];
        
        for (int i = 1; i < n + 1; i++) {
            // 最大值即为i
            dp[i] = i;

            // 转移方程
            for (int j = 1; i - j*j >= 0; j++) {
                dp[i] = Math.min(dp[i], dp[i - j*j] + 1);
            }
        }

        return dp[n];
    }
}

推荐题解:画解算法:279. 完全平方数

283. 移动零

思路一:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就插入 i 指向的位置并一起向后移动,最后将 i 及其后面全部补0即可。

class Solution {
    public void moveZeroes(int[] nums) {
        int len = nums.length;
        
        int i = 0;  // i 指向 非0 元素的下一个待插入位置

        // j 一直向后遍历,每次遇到 非0 元素就插入i指向的位置并一起向后移动
        for (int j = 0; j < len; j++) {
            if (nums[j] != 0) {
                nums[i++] = nums[j];
            }
        }

        // i 及其后面全部补0即可
        for ( ; i < len; i++) {
            nums[i] = 0;
        }
    }
}

思路二:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就与 i 指向元素交换并一起向后移动,最后 [0, i) 之前全部为非0元素,(i, j)之间全部为 0。

class Solution {
    public void moveZeroes(int[] nums) {
        int len = nums.length;
        
        int i = 0;  // i 指向 非0 元素的下一个待插入位置

        // 每次遇到 非0 元素就与 i 指向元素交换并一起向后移动
        for (int j = 0; j < len; j++) {
            if (nums[j] != 0) {
                swap(nums, i, j);
                i++;
            }
        }
        
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

287. 寻找重复数

该题最简单的思路便是使用set,但题目要求空间复杂度为O(1)。另外一种简单思路是使用排序,然后检查重复数即可,但题目又要求时间复杂度为O(nlogn),且不能修改原数组。面试时我们应该询问面试官 时间 和 空间复杂度 要求之后再确定具体使用的方法。

思路一:快慢指针,建议先刷:141. 环形链表142. 环形链表 II

推荐题解:287.寻找重复数

class Solution {
    public int findDuplicate(int[] nums) {
        int slow = 0;
        int fast = nums[slow];

        while (slow != fast) {
            fast = nums[nums[fast]];
            slow = nums[slow];
        }

        fast = nums[slow];
        slow = 0;

        while (slow != fast) {
            fast = nums[fast];
            slow = nums[slow];
        }

        return slow;
    }
}

思路二:将所有元素放置在其值减1的下标位置。如:1 应该放置在 0 位置,3 应该放置在 2 位置,这样,重复的元素至少有一个无法放置在对应位置。思路来自:剑指 Offer 03. 数组中重复的数字

class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length;

        for (int i = 0; i < len - 1; i++) {
            // 如果元素的 值 和 下标 不匹配,则将其交换至对的位置
            while ((nums[i] - 1) != i) {
                // 如果发现待交换的两个元素相同则直接返回 如:[3,1,3,4,2]
                if (nums[i] == nums[nums[i] - 1]) {
                    return nums[i];
                }
                swap(nums, i, nums[i] - 1);
            }
        }

        // 通常,最后一个元素为重复元素
        return nums[len - 1];
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp; 
    }
}

备注:该方法修改了原来的数组。时间复杂度为O(n),因为每个元素至多交换一次,空间复杂度为O(1)

297. 二叉树的序列化与反序列化

思路一:使用先序遍历进行序列化(dfs)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) {
            return "#";
        }

        String leftSerial = serialize(root.left);
        String rightSerial = serialize(root.right);

        return root.val + "," + leftSerial + "," + rightSerial;
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] strs = data.split(",");
        Deque<String> queue = new LinkedList<>();
        for (String str : strs) {
            queue.offer(str);
        }

        return deserialize(queue);
    }

    private TreeNode deserialize(Deque<String> queue) {
        
        String rootVal = queue.poll();
        if (rootVal.equals("#")) {
            return null;
        }

        TreeNode root = new TreeNode(Integer.parseInt(rootVal));
        root.left = deserialize(queue);
        root.right = deserialize(queue);

        return root; 
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

思路二:使用层次遍历进行序列化(bfs)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        Deque<TreeNode> queue = new LinkedList<>();
        StringBuilder sb = new StringBuilder();

        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode temp = queue.poll();

            if (temp != null) {
                sb.append(temp.val).append(",");

                queue.offer(temp.left);
                queue.offer(temp.right);
            } else {
                sb.append("#").append(",");
            }
        }
        System.out.println(sb.toString());
        return sb.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] strs = data.split(",");
        Deque<TreeNode> queue = new LinkedList<>();
        
        int i = 0;
        if (strs[i].equals("#")) return null;
        
        TreeNode root = new TreeNode(Integer.parseInt(strs[i++]));
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode temp = queue.poll();

            String leftVal = strs[i++];
            String rightVal = strs[i++];

            if (!leftVal.equals("#")) {
                TreeNode leftChild = new TreeNode(Integer.parseInt(leftVal));
                temp.left = leftChild;
                queue.offer(leftChild);
            }
            if (!rightVal.equals("#")) {
                TreeNode rightChild = new TreeNode(Integer.parseInt(rightVal));
                temp.right = rightChild;
                queue.offer(rightChild);
            }
        }
        
        return root;
    }


}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

推荐题解:『手画图解』剖析DFS、BFS解法 | 二叉树的序列化与反序列化

300. 最长递增子序列

思路:动态规划

状态:以当前字符结尾的字符串中最长递增子序列的长度

转移方程:dp[i] = max(dp[j] + 1, dp[i]),其中 j < inums[j] < nums[i]

base case:dp[i] = 1

class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;

        // dp[i] 表示以当前字符结尾的字符串中最长递增子序列的长度
        int[] dp = new int[len];

        int maxLen = 0;
        for (int i = 0; i < len; i++) {
            //base case
            dp[i] = 1;
            
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }

            maxLen = Math.max(maxLen, dp[i]);
        }

        return maxLen;
    }
}

301. 删除无效的括号

思路:回溯法


class Solution {
    public List<String> removeInvalidParentheses(String s) {
        // 左括号和右括号最少需要移除的个数
        int leftRemove = 0, rightRemove = 0;

        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                leftRemove++;
            } else if (s.charAt(i) == ')') {
                //存在左括号则消去一个
                if (leftRemove > 0) {
                    leftRemove--;
                } else if (leftRemove == 0) {
                    rightRemove++;
                }
            }
        }

        StringBuilder track = new StringBuilder();
        dfs(s, 0, track, 0, 0, leftRemove, rightRemove);
        return new ArrayList<>(res);
    }

    private Set<String> res = new HashSet<>();

    private void dfs(String s, int index, StringBuilder track, int leftCnt, int rightCnt, int leftRemove, int rightRemove) {
        if (s.length() == index) {
            if (leftRemove == 0 && rightRemove == 0) {
                res.add(track.toString());
            }

            return;
        }

        char ch = s.charAt(index);

        // 选择1:删除当前字符
        if (ch == '(' && leftRemove > 0) {
            dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove - 1, rightRemove);
        } else if (ch == ')' && rightRemove > 0) {
            dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove - 1);
        }

        // 选择2:保留当前字符
        track.append(ch);
        if (ch == '(' ) {
            dfs(s, index + 1, track, leftCnt + 1, rightCnt, leftRemove, rightRemove);
        } else if (ch == ')' && leftCnt > rightCnt) {
            dfs(s, index + 1, track, leftCnt, rightCnt + 1, leftRemove, rightRemove);
        } else if (ch != '(' && ch != ')') {
            dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove);
        }
        track.deleteCharAt(track.length() - 1);
    }
}

推荐题解:删除无效的括号

309. 最佳买卖股票时机含冷冻期

思路:动态规划

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int[][] dp = new int[len][2];

        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        for (int i = 1; i < len; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], (i >= 2 ? dp[i - 2][0] : 0) - prices[i]);
        }

        return dp[len - 1][0];
    }
}

312. 戳气球

思路:动态规划。推荐题解:[这个菜谱, 自己在家也能做] 关键思路解释

class Solution {
    public int maxCoins(int[] nums) {
        int len = nums.length;
        // 前后两位各插入1
        int[] newNums = new int[len + 2];
        newNums[0] = 1;
        newNums[len + 1] = 1;
        for (int i = 0; i < len; i++) {
            newNums[i + 1] = nums[i];
        }

        //dp[i][j] 代表 戳爆开区间 (i, j) 中的气球所能取得的最大值
        //默认值都为0,base case: dp[i][j] = 0, j - i < 3
        int[][] dp = new int[len + 2][len + 2]; 

        for (int size = 3; size <= len + 2; size++) {
            //开区间起始位置
            for (int start = 0; start <= len + 2 - size; start++) {
                int max = 0;
                //列举最后一个被戳爆的气球为 newNums[k]的所有情况, K属于[start + 1, start + size - 1),找到最大值
                for (int k = start + 1; k < start + size - 1; k++) {
                    max = Math.max(max, dp[start][k] + dp[k][start + size - 1] + newNums[start] * newNums[k] * newNums[start + size - 1]);
                }
                dp[start][start + size - 1] = max;
            }
        }

        return dp[0][len + 1];
    }
}

322. 零钱兑换

思路:动态规划。

class Solution {
    public int coinChange(int[] coins, int amount) {
        //状态:dp[i] 表示凑够i需要的最少硬币数
        int[] dp = new int[amount + 1];
        //求最小值,先初始为足够大。(若能凑成,最多需要amount枚硬币)
        Arrays.fill(dp, amount + 1);   

        //base case
        dp[0] = 0;

        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                //当前背包(总金额)若能装下物品(硬币面额)
                if (i >= coins[j]) {
                    dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
        }

        return dp[amount] >= amount + 1 ? -1 : dp[amount];
    }
}
posted @ 2021-07-17 04:39  WINLSR  阅读(54)  评论(0编辑  收藏  举报