期末算是过去了,接下来时要为了秋招好好准备了。
文章目录
- 6.24
- 6.25
- 6.26
- [面试题 02.01. 移除重复节点](https://leetcode-cn.com/problems/remove-duplicate-node-lcci/)
- [96. 不同的二叉搜索树](https://leetcode-cn.com/problems/unique-binary-search-trees/)
- [1139. 最大的以 1 为边界的正方形](https://leetcode-cn.com/problems/largest-1-bordered-square/)
- [面试题 08.11. 硬币](https://leetcode-cn.com/problems/coin-lcci/)
- [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/)
- [213. 打家劫舍 II](https://leetcode-cn.com/problems/house-robber-ii/)
- [337. 打家劫舍 III](https://leetcode-cn.com/problems/house-robber-iii/)
6.24
16. 最接近的三数之和
难度中等
暴力法的时间复杂度能到达三次方。为了简化时间复杂度,先固定一个值,对其后面的数值进行双指针的查找,找到三数之和与目标值相差最小的组合。
class Solution {
public int threeSumClosest(int[] nums, int target) {
int minDiff = Integer.MAX_VALUE;
int ans = 0;
Arrays.sort(nums);
for(int i = 0;i < nums.length;i++){
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
int j = i + 1;
int m = nums.length - 1;
while(j < m){
int sum = nums[i] + nums[j] + nums[m];
if(sum == target){
return sum;
}else if(sum > target){
if(minDiff > sum - target){
ans = sum ;
minDiff = sum - target;
}
m--;
}else if(sum < target){
if(minDiff > target - sum){
ans = sum ;
minDiff = target - sum;
}
j++;
}
}
}
return ans;
}
}
6.25
139. 单词拆分
难度中等
原来以为s要包含wordDict中所有的字符串,并且至少使用一次。写了之后发现不对,之后发现其实只要s能被wordDict中的部分字符串组成就可以了。这样的话就可以用动态规划的思路来实现。
首先初始化一个长度length+1的boolean数组,用来表示到s的i位能不能被组合。把list转成set方便进行包含关系的判断。两重循环就可解决。
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;//长度为0一定能组成
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
6.26
面试题 02.01. 移除重复节点
难度简单
利用hashSet来存储已经出现过的数字。用两个节点来记录遍历,最后返回头结点即可。
思想就是利用set,遍历链表时,如果出现未保存过的数值,则更新实际的链表;如果出现已保存的数值则不做任何操作。当更新了node之后,需要将实际链表的末尾置为null,因为之后无法判断是否已经到达末尾(是否有重复)。这样就能得到答案。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeDuplicateNodes(ListNode head) {
if(head == null){
return null;
}
ListNode temp = new ListNode();
ListNode node = head;
Set<Integer> set = new HashSet<>();
while(node != null){
if(!set.contains(node.val)){
set.add(node.val);
temp.next = node;
temp = temp.next;
}
node = node.next;
if(node != null && set.contains(node.val)){
temp.next = null;
}
}
return head;
}
}
96. 不同的二叉搜索树
难度中等
这个题之前春招面试问到过,结果没做出来。当时以为是要从数学的角度出发,没有想到之后可以转变到动态规划的问题。
初始化一个数组,第i位标识i个节点所能组成的所有二叉树的数量总数。
当有n个节点时,那么它的子树的节点总数是n-1。然后去遍历之前的dp辅助数组。主要是添加一个当左右子树的节点为0时的特殊处理,直接加上即可(这里的特殊处理花了我好长时间)。当左右子树都有节点时,则将左子树的节点个数所能组成的二叉树总量 乘上 右子树的节点个数所能构成的二叉树总量,遍历相加即可。
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
for(int i = 2;i < dp.length;i++){
for(int j = i - 1;j >= 0;j--){
if(dp[i - j - 1] != 0 && dp[j] != 0){
dp[i] += dp[j] * dp[i - j - 1];//左子树个数 * 右子树个数
}else if(dp[j] != 0){
dp[i] += dp[j];
}else if(dp[i - j - 1] != 0){
dp[i] += dp[i - j - 1];
}
}
}
return dp[n];
}
}
1139. 最大的以 1 为边界的正方形
难度中等
一维的动态规划刚有点感觉,这二维的动态规划…关键是要知道构建三维数组,借助数组能获取到之前的左边和上面最长的连续的1的个数。借此做判断可不可以够长正方形。
哎,最后还是抄了题解才知道解法。
// 使用3维数组dp[n + 1][m + 1][2](数组下标从1开始)
// dp[i][j][0]:表示第i行第j列的1往 左边 最长连续的1的个数
// dp[i][j][1]:表示第i行第j列的1往 上面 最长连续的1的个数
class Solution {
public int largest1BorderedSquare(int[][] grid) {
int n = grid.length, m = 0, res = 0;
if(n == 0) return 0;
m = grid[0].length;
int[][][] dp = new int[n + 1][m + 1][2];
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
int d = 0;
if(grid[i - 1][j - 1] == 1){ //因为dp数组下标从1开始
//更新dp[i][j]的2个状态
dp[i][j][0] = dp[i][j - 1][0] + 1;
dp[i][j][1] = dp[i - 1][j][1] + 1;
//以当前的1为右下角要构成正方形,其边长最长只可能是左边最长连续的1与上面最长连续的1的个数取最小值
d = Math.min(dp[i][j - 1][0], dp[i - 1][j][1]); //d表示不包括当前1的最长长度
while(d > 0){
//判断第i行的第j-d列的上面1的个数,和第i-d行的第j列的左边的1的个数是否都大于d(大于d,不能等,因为d比实际边长小1)
if(dp[i][j - d][1] > d && dp[i - d][j][0] > d)
break; //判断边长为d能否构成正方形,若能break
d--; //若不能就取次小的边长
}
res = Math.max(res, d + 1);
}
}
}
return res * res;
}
}
面试题 08.11. 硬币
难度中等
发现硬币类的问题都是借助硬币数组来帮助实现。需要注意的是,当n=0的时候是返回1,没有表示方法也是一种方法。(当i=coin时,默认情况i - coin应该等于1)
class Solution {
public int waysToChange(int n) {
int[] dp = new int[n + 1];
int[] coins = new int[]{1,5,10,25};
dp[0] = 1;
for(int coin : coins){
for(int i = 0;i <= n;i++){
if(i >= coin)
dp[i] = (dp[i] + dp[i - coin]) % 1000000007;
}
}
return dp[n];
}
}
198. 打家劫舍
难度简单
接下来做动态规划的经典,打家劫舍系列。借助一个数组来帮助实现。
每次到下一家,可以选择偷或者不偷。偷或者不偷的标准在于是如果不偷,那么到达前一个房屋的最大金额和选择偷的时候前两个房屋的最大金额加上这家的金额相比较。
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[1], dp[0]);
for(int i = 2;i < nums.length;i++){
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[dp.length - 1];
}
}
也可以用两个变量来减少空间复杂度。
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
int f1 = nums[0];
int f2 = Math.max(nums[1], nums[0]);
int ans = f2;
for(int i = 2;i < nums.length;i++){
ans = Math.max(f2, f1 + nums[i]);
f1 = f2;
f2 = ans;
}
return ans;
}
}
213. 打家劫舍 II
难度中等
在前一题的基础上增加了首位相连的条件,那么小偷要么偷第一家,不偷最后一家,要么是不偷第一家,而偷最后一家。这样一想,这个题目和上一题一模一样。只需要取出这两种情况的最大值即可。
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
if(nums.length == 2){
return Math.max(nums[0], nums[1]);
}
return Math.max(rob(nums, 0, nums.length - 2), rob(nums, 1, nums.length - 1));
}
public int rob(int[] nums, int begin, int end){
int f1 = nums[begin];
int f2 = Math.max(nums[begin + 1], nums[begin]);
int ans = f2;
for(int i = begin + 2;i <= end;i++){
ans = Math.max(f2, f1 + nums[i]);
f1 = f2;
f2 = ans;
}
return ans;
}
}
337. 打家劫舍 III
难度中等
原来以为用广度优先遍历就能解决,不能投直接相连的节点,那么小偷只能偷奇数层或偶数层,得到两者的总和取较大值就行了。可惜啊,事情没那么简单。小偷可以偷根节点和下两层的节点。从头再来。
树的动态规划是真的没什么思路。参照了大佬的题解,才有点想法。
主要思想:
当我们到达一个节点时有两种选择。一个是偷,一个是不偷。
当我们选择不偷时,我现在的最大数值就是左子树和右子树的最大数值之和(可以选择偷左右子树的根节点)
当我们选择偷时,我现在的最大数值就是该节点的价值+左右子树不偷根节点的最大价值之和
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}
public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left);
int[] right = robInternal(root.right);
//0代表不偷自己,去偷子树
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
//1代表偷自己,自己的价值再加上左右子树不偷自己的数值
result[1] = left[0] + right[0] + root.val;
return result;
}
}