程序员老鬼

现在老公失业都不敢说话,烦死了,真的好无奈一点处理问题的能力都没有。

刚看到个贴子,说老公失业在家,一点主意没有,还不敢吭声,把老婆烦得要死。看着确实憋屈。

Image

这事吧,先别急着给谁判死刑。失业真的挺常见的,不丢人,丢人的是装没事、不沟通、不行动。

从我的角度看,问题关键有两个: 一是男人的面子病,越失败越沉默,把自己关机,以为不说就不会被嫌弃; 二是女人的委屈积累太久,只看到“没用”,看不到“害怕”。一个不说,一个只怨,这日子肯定越过越冷。

但话说回来,理解归理解,行动还是要有的。哪怕先去干份临时工,主动商量家庭收支,给伴侣一点“我在想办法”的安全感,比闷头刷手机强多了。

失业只是阶段,躺平才是问题。夫妻能一起面对现实、一起想路子,比什么甜言蜜语都靠谱。

面试题:三数之和

写这个三数之和的时候,我脑子里老是想起一个画面:晚上十点多,办公室只剩你一个人,面前一堆力扣标签写着“中等”,点进去一看——三数之和。心里想一句:看着不难啊,但怎么老是过不去…

先把题目说清楚一点:给你一个 int 数组 nums,要找出所有「不重复」的三元组 nums[i] + nums[j] + nums[k] == 0,下标不能重复,而且结果里不能有重复的三元组,比如 [-1,0,1] 只能出现一次。

很多人一上来脑子里就是暴力:三层 for,把所有三元组枚举一遍,和等于 0 就丢进结果里。时间复杂度直接 O(n^3),数据量一大,在线评测就直接给你超时警告,面试官脸色也不好看。

这个题其实有点套路味道:一看就是「几数之和」家族里的老大哥,思路基本一样:排序配合双指针。大致想法是这样的:

先给数组排序,这一步非常关键。一旦有序,就可以用两端往中间夹的方式去找目标值,不用到处乱试。

排序完之后,固定第一个数 nums[i],问题就变成了:在 i+1 … n-1 这一段里找两个数,让它们的和等于 -nums[i]。这不就退化成两数之和了嘛,只不过这里用的是双指针解法。

细一点展开说下流程(脑子里自己演一遍就很清楚):

数组升序排好,比如 [-4,-1,-1,0,1,2]。

选定一个下标 i,当前数是 nums[i]。

在右边开两个指针:left = i + 1 指向左边界,right = n - 1 指向最右边。

每次算 sum = nums[i] + nums[left] + nums[right]: 如果 sum == 0,说明撞上一个答案,收下。 如果 sum < 0,说明偏小了,因为数组有序,要想变大,只能把左指针往右挪。 如果 sum > 0,说明偏大了,只能把右指针往左挪。

这就保证了指针每次都在往中间收拢,不会反复横跳,整体复杂度就是 O(n^2) 级别,排序 O(n log n) 加上双指针 O(n^2),最后是 O(n^2) 为主。

真正写代码的时候,坑主要在「去重」。题目有两个不重复: 一个是三元组本身不能重复; 一个是同一个位置的数字,不能无限当起点反复算出相同的三元组。

去重其实就两层:

外层 i 去重: 如果当前 nums[i] 和前一个相同,那以它为起点能找到的三元组,之前那个 i-1 已经找过了,可以直接跳过。

内层 left/right 去重: 当 sum == 0 找到一个三元组后,如果下一个 nums[left + 1] 跟现在这个一样,就没必要再拿这个值配了,移动指针跳过重复值即可,right 同理。

整合一下,上代码,用 Java 写一个比较标准的版本:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

publicclassThreeSumSolution{

public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length < 3) {
return res;
        }

// 排序是后面所有操作的基础
        Arrays.sort(nums);

int n = nums.length;
for (int i = 0; i < n - 2; i++) {
// 这个起点已经处理过了,跳过
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
            }

// 如果当前已经大于 0,再往后只会更大,不可能有和为 0 的三元组了
if (nums[i] > 0) {
break;
            }

int left = i + 1;
int right = n - 1;

while (left < right) {
int sum = nums[i] + nums[left] + nums[right];

if (sum == 0) {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));

// 跳过所有和当前 left 相同的值
while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
// 跳过所有和当前 right 相同的值
while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }

// 真正移动到下一组
                    left++;
                    right--;
                } elseif (sum < 0) {
                    left++;   // 和太小,左指针往右挪
                } else {
                    right--;  // 和太大,右指针往左挪
                }
            }
        }

return res;
    }

// 随便写个 main 做个小测试
publicstaticvoidmain(String[] args){
        ThreeSumSolution solution = new ThreeSumSolution();
int[] nums = {-1, 0, 1, 2, -1, -4};
        List<List<Integer>> ans = solution.threeSum(nums);
        System.out.println(ans);
// 期望输出类似:[[-1, -1, 1], [-1, 0, 1]]
    }
}

里面几个小细节再提醒一下,不少人是被这些细节卡住的:

数组长度小于 3 直接返回空列表,别让下标把你自己干崩了。

nums[i] > 0 的剪枝很划算,一旦第一个数都大于 0,后面全是更大的正数,三数之和不可能再变成 0,可以直接退出循环,省不少时间。

跳重的时候,记得是「在找到一个解之后」再去 while 跳,不然会跳过合法情况;并且外层 i 的去重和内层 left/right 的去重要分清,位置搞错就会漏解或者重复。

如果你之后刷到四数之和、k 数之和,其实都是在这个套路上继续往外展开:再多固定一个数,剩下用三数之和或者两数之和去解,思路是一脉相承的。

写多了你会发现,这种「先排序,再双指针」的方式,在很多地方都有影子,跟之前做数据库那种按主键查、按范围扫其实一个味道,都是利用有序这件事省力气。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html