查看: 1409| 回复: 0
跳转到指定楼层
上一主题 下一主题
收起左侧

[其他] 举几个例子,具体说一下面试的时候最优解和次优解的取舍。

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
本帖最后由 不知道小帅 于 2020-6-14 09:34 编辑

第一个例子,也是一个很高频的题目。
https://leetcode.com/problems/longest-increasing-subsequence/
很经典的dp问题。
  1. public class Solution {
  2.     public int lengthOfLIS(int[] nums) {
  3.         if (nums.length == 0) {
  4.             return 0;
  5.         }
  6.         int[] dp = new int[nums.length];
  7.         dp[0] = 1;
  8.         int maxans = 1;
  9.         for (int i = 1; i < dp.length; i++) {
  10.             int maxval = 0;
  11.             for (int j = 0; j < i; j++) {
  12.                 if (nums > nums[j]) {
  13.                     maxval = Math.max(maxval, dp[j]);
  14.                 }
  15.             }
  16. [i]            dp[i] = maxval + 1;
  17.             maxans = Math.max(maxans, dp);
  18.         }
  19.         return maxans;
  20.     }
  21. }
复制代码

这个是最容易想到的,也比较不容易出错。
第二种使用二分查找的算法代码如下:
  1. public class Solution {
  2.     public int lengthOfLIS(int[] nums) {            
  3.         int[] dp = new int[nums.length];
  4.         int len = 0;

  5.         for(int x : nums) {
  6.             int i = Arrays.binarySearch(dp, 0, len, x);
  7.             if(i < 0) i = -(i + 1);
  8.             dp[i] = x;
  9.             if(i == len) len++;
  10.         }

  11.         return len;
  12.     }
  13. }
复制代码

第二种解法还是比较难以解释的。比如,这个dp数组的意义是什么?前len个数,是不是就等同于最长递增子序列?如果面试官让你输出最长递增子序列,你应该怎么办?
如果包括但不限于这几个问题,你可以很轻松掌握,并且有信心给面试官讲明白,你当然是可以直接给出这个解法。但是如果你不那么自信的话,建议你先把常见的dp写法给出来。

第二个例子,PathSumIII。
https://leetcode.com/problems/path-sum-iii/
第一种最容易想到的解法,
  1. public class Solution {
  2.     public int pathSum(TreeNode root, int sum) {
  3.         if (root == null) return 0;
  4.         return pathSumFrom(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
  5.     }
  6.    
  7.     private int pathSumFrom(TreeNode node, int sum) {
  8.         if (node == null) return 0;
  9.         return (node.val == sum ? 1 : 0)
  10.             + pathSumFrom(node.left, sum - node.val) + pathSumFrom(node.right, sum - node.val);
  11.     }
  12. }
复制代码


这种办法的复杂度最差是n^2, 平均nlogn。
第二种办法是采用prefix Sum,以及HashMap,可以达到O(n)的复杂度。
  1.     public int pathSum(TreeNode root, int sum) {
  2.         HashMap<Integer, Integer> preSum = new HashMap();
  3.         preSum.put(0,1);
  4.         return helper(root, 0, sum, preSum);
  5.     }
  6.    
  7.     private int helper(TreeNode root, int currSum, int target, HashMap<Integer, Integer> preSum) {
  8.         if (root == null) {
  9.             return 0;
  10.         }
  11.         
  12.         currSum += root.val;
  13.         int res = preSum.getOrDefault(currSum - target, 0);
  14.         preSum.put(currSum, preSum.getOrDefault(currSum, 0) + 1);
  15.         
  16.         res += helper(root.left, currSum, target, preSum) + helper(root.right, currSum, target, preSum);
  17.         preSum.put(currSum, preSum.get(currSum) - 1);
  18.         return res;
  19.     }
复制代码


这种办法确实复杂度更好,但是你需要注意的细节也有不少。如果只是问是否存在,这种做法就类似2sum,很好写。但是求数量会有一些细节问题。
如果你掌握的很好,自然是一个加分项。我的建议就是先写出最基本的recursion/dfs的做法,然后再给出这个优化与实现。这样至少有一个保底。

第三个例子,subset
https://leetcode.com/problems/subsets/
最基本的做法,自然是dfs/backtracking了。

  1. public class Solution {

  2.     public List<List<Integer>> subsets(int[] nums) {
  3.         int size = nums.length;
  4.         List<List<Integer>> res = new ArrayList<>();
  5.         if (size == 0) {
  6.             return res;
  7.         }
  8.         Stack<Integer> stack = new Stack<>();
  9.         for (int i = 0; i < size + 1; i++) {
  10.             dfs(nums, 0, i, stack, res);
  11.         }
  12.         return res;
  13.     }

  14.     private void dfs(int[] nums, int start, int depth, Stack<Integer> path, List<List<Integer>> res) {
  15.         if (depth == path.size()) {
  16.             res.add(new ArrayList<>(path));
  17.             return;
  18.         }
  19.         for (int i = start; i < nums.length; i++) {
  20.             path.add(nums[i]);
  21.             dfs(nums, i + 1, depth, path, res);
  22.             path.pop();
  23.         }
  24.     }

  25. }
复制代码


当然,也有炫技一点的写法。利用位掩码。

  1. public class Solution {

  2.     public List<List<Integer>> subsets(int[] nums) {
  3.         int size = nums.length;
  4.         int n = 1 << size;
  5.         List<List<Integer>> res = new ArrayList<>();

  6.         for (int i = 0; i < n; i++) {
  7.             List<Integer> cur = new ArrayList<>();
  8.             for (int j = 0; j < size; j++) {
  9.                 if (((i >> j) & 1) == 1) {
  10.                     cur.add(nums[j]);
  11.                 }
  12.             }
  13.             res.add(cur);
  14.         }
  15.         return res;
  16.     }
  17. }
复制代码


位掩码的代码短,看着也精巧。
但是其实面试官更注重dfs之类的基本功。全写出来可以用来炫技。

第四个例子,confusing number
这是一道收费题目,我把题目复制过来。
代码稍微有点繁琐,我就不贴了。有兴趣的话我在后面贴。


最直接想法就是从1遍历到n,然后看是否confuse,这样的复杂度是O(nlogn),TLE!
第二种做法就是枚举所有可能性,因为只有0 1 6 8 9可能混淆,总共需要枚举的次数也就是5^(log10n)
第三种做法,也就是最优解,是数位dp。复杂度只有O(logn), 但是我强烈不建议大家面试的时候写。
一是难想,二是难debug,细节又太多。
个人水平我觉得应该还是可以的,contest基本也可以一小时内写完全四道题目,leetcode基本上可以不看题解写出大部分题目。
我自己尝试去实现数位dp,加上debug,非面试状态下花了一个半小时以上才调试好,而且写得很长很丑。面试时间有限,几乎不可能写出来。
当然大神除外。。
举这几个例子的意思就是说,很多时候,面试官想要考察的是基本功,所以,一些通用的套路和写法一定要会写。当然,一些好的算法和最优解肯定是加分项,前提是你理解清楚。
然后就是说,一些偏门的奇技淫巧可能没有那么大意义,稍微一点变化很可能就答不上来。
所以,面试的时候,不停沟通,要展示出自己会写通用的解法,了解基本的套路。然后,码完之后你可以跟面试官提,你有更好的解法,但是一定要解释得清楚。


[/i]

评分

参与人数 6大米 +14 收起 理由
yyt913 + 5 给你点个赞!
我想要offer真的 + 1 给你点个赞!
jack晓峰 + 2 面试中真的很难取舍!谢谢建议!
lancurs + 2 欢迎分享你知道的情况,会给更多积分奖励!
hoooga + 2 很有用的信息!

查看全部评分


上一篇:想问下leetcode 29一个神奇的地方
下一篇:merge intervals, 关于重载cmp函数
🔗
jack晓峰 2020-11-17 09:01:14 | 只看该作者
全局:
面试中真的很难取舍!谢谢建议!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表