【学习】大厂经典算法题整理(一)
前言
整个一月学习状态其实挺好的,结果临近过年节奏打乱了....之前的很多计划都没有如期实施,不爽...这篇算是年后第一篇整理性质的文章,也算是找找状态。
最近看了左程云老师的算法课,觉得讲的通透也高效,恰逢其举了几个经典例题,故在此记录,方便后续复习。
小红书
题目描述:
摸着石头过河
* [0,4,7] 0 表示这里石头没有颜色,变红的代价是4,变蓝的代价是7.
* [1,X,X] 1 表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义
* [2,X,X] 2 表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义
* 颜色只可能是0、1、2,代价一定>=0
* 给你一批这样的数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价
* 如果怎么都无法做到,返回-1
解题思路:
首先找到边界条件:
如果石头总数为奇数,则无论如何都无法做到
如果给定带颜色石头的单一颜色数量超过总数n的一半,则无论如何都无法做到
之后,可以依照贪心的思路,将没有颜色的石头都刷成一种颜色,之后再从这些石头中,找出变色代价最小的几个石头做变色,这里【变色代价】其实就是红色代价减去蓝色代价(默认刷红色,反之亦然),如[0,4,7],若将该石头先刷为红色,则当前代价为4,将其改为蓝色后的新代价为4-(4-7).
那么如何找到最小的几个【变色代价】呢,直接按照【变色代价】进行排序,再从低到高取石头进行变色即可
实例代码:
public class Code01_xiaohongshu {
public static int minCost(int[][] stones){
if (stones == null || stones.length < 2){
// 为空或者只有一个,做不到
return -1;
}
int n = stones.length;
if (n%2 != 0){
// 如果是奇数,也不行
return -1;
}
int redStoneNums = 0; // 蓝色数量
int blueStoneNums = 0; // 红色数量
int cost = 0; // 总花销
for (int[] temp : stones){
// 统计每种颜色个数,并计算全为红色的代价
if (temp[0] == 1){
redStoneNums ++;
} else if (temp[0] == 2){
blueStoneNums ++;
} else {
cost += temp[1]; // 默认全给红色
}
}
if (redStoneNums > n/2 || blueStoneNums > n/2){
return -1; // 任意一种颜色大于总数的一半,则做不到
}
Arrays.sort(stones,(a,b) -> a[0]==0 && b[0]==0 ? b[1]-b[2]-a[1]+a[2] : a[0]-b[0]);
// 以红色成本-蓝色成本的差(变色代价)作为排序标准,降序排列,
// 因为默认将所有球归为红色,而降序后靠前的元素其更改为蓝色后的成本缩减幅度最大
for (int i = 0 ; i < n/2-blueStoneNums ; i++){
// 计算将这些石头变为蓝色后的代价
cost -= stones[i][1]-stones[i][2];
}
return cost;
}
}
网易
题目描述:
环形糖果
* 给定一个正数数组arr,表示每个小朋友的得分
* 任何两个相邻的小朋友如果得分一样,怎么分糖果无所谓
* 但如果得分不一样
* 分数大的一定要比分数少的多拿一些糖果
* 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数
解题思路:
这道题可以先不考虑环形
将题目变成:一排小朋友依照邻居的分数进行糖果的分配
那么,每一名小朋友的糖果仅需要与左右比较即可(可以暴力递归)
用[3,4,3,2,1]举例
先单向的从左向右考虑:
arr[0]位置的孩子左侧没有人,则其糖果数目规定为1
arr[1]>arr[0],糖果为arr[0]+1,2
arr[2]<arr[1],则给1
则得到一组根据左手边分数分配糖果的数组:left[1,2,1,1,1]
同理,再单向的从右向左考虑得到:right[1,4,3,2,1]
最后在每个位置上取max即为最终结果:[1,4,3,2,1]
引入环形后,需要考虑边界的两个孩子分数大小,此时可以将环形化为单向问题
从数组中找到一个极小值,即arr[j-1] > arr[j] < arr[j+1],将j作为排头即可
实例代码:
public class Code02_wangyi {
public static int nextIndex(int i, int n) {
return i == n - 1 ? 0 : (i + 1);
}
public static int lastIndex(int i, int n) {
return i == 0 ? (n - 1) : (i - 1);
}
public static int minCandy(int[] arr){
if (arr == null){
return 0;
}
if (arr.length < 2){
return 1;
}
int startLoc = 0;
int n = arr.length;
for (int i = 0 ; i < n ; i++){
// 找到极小位置
if (arr[i] <= arr[lastIndex(i,n)] && arr[i] <= arr[nextIndex(i,n)]){
// i位置为局部最小,作为起始点
startLoc = i;
break;
}
}
// 此时startLoc 为 起始点
int[] nums = new int[n+1];
// 之所以是n+1,是因为将排头也放在了末尾,方便n-1位置的孩子与n位置(0位置)进行比较
for (int i = 0 ; i < n ; i++, startLoc = nextIndex(startLoc,n)){
nums[i] = arr[startLoc]; // 调整顺序,得到新数组即可单向查找
}
int[] left = new int[n + 1];
left[0] = 1;
for (int i = 1; i <= n; i++) {
left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1;
// 与左侧邻居比较
}
int[] right = new int[n + 1];
right[n] = 1;
for (int i = n - 1; i >= 0; i--) {
right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1;
// 与右侧邻居比较
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += Math.max(left[i], right[i]);
// 汇总取max
}
return ans;
}
}
腾讯
题目描述:
坐船
* 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重
* 所有船都是同样的载重量,每个人的体重都一定不大于船的载重
* 要求:
* 1. 可以1个人单独一搜船
* 2. 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重
* 3. 一艘船最多坐2人
* 返回如果想所有人同时坐船,船的最小数量
解题思路:
与上面的题类似,可以先不考虑奇偶问题,先考虑如何组合是最小的。
最小的可能一定是:能两人坐就两人坐,剩下的人再一人一船。(听着像废话哈~)
那么就需要先考虑哪些人能量量配队,且其是最优配队。
第一阶段:
可以将数组arr(递增的)划分为 arr[0..i] < limit/2 , arr[i+1..] > limit/2这两部分
此时左侧最大与右侧的最小先尝试能否上船,如果不行,在左侧区域向左找体重小的,直到能够上船
之后在右侧向右找体重大的,直到上不去船,此时右侧移动了几位就能够上船几组。
直到左侧或右侧没有人选后,开始第二个阶段。
第二阶段:
左侧剩下的人两两组合上船,因为左侧部分一定是小于limit/2的,那么两两组合一定小于limit,
之后右侧一人一船,即完成了所有人登船且船数量最小。
那么考虑进两人相加为偶数的化,只需要什么样的两两组合为偶数即可。
奇数+奇数,偶数+偶数
因此在进行上述两个阶段前,将arr数组奇偶分开,分别求最少船数再相加就行了。
实例代码:
public class Code01_tengxun {
public static int minBoat(int[] arr, int limit) {
if (arr == null || arr.length < 1){
return 0;
}
Arrays.sort(arr);
if (arr[arr.length-1] > limit){
// 排序后,最重的人若是超过船的限制,则表示不行
return 0;
}
int odd = 0; // 奇数数量
int even = 0; // 偶数数量
for (int num : arr) {
if ((num & 1) == 0){
// 偶数,因为偶数二进制低位一定为0
even++;
} else {
odd++;
}
}
int[] oddsNum = new int[odd];
int[] evensNum = new int[even];
for (int i = arr.length-1 ; i >= 0 ; i--){
// 奇偶分成两个数组
if ((arr[i]&1) == 0){
evensNum[--even] = arr[i];
} else {
oddsNum[--odd] = arr[i];
}
}
return getMinValue(oddsNum,limit) + getMinValue(evensNum,limit);
}
public static int getMinValue(int[] arr, int limit) {
if (arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
if (arr[N - 1] > limit) {
return -1;
}
int lessR = -1;
for (int i = N - 1; i >= 0; i--) {
// 找到分界点
if (arr[i] <= (limit / 2)) {
lessR = i;
break;
}
}
if (lessR == -1) {
// 如果为-1则表明arr全小于limit/2
return N;
}
// 声明两个指针负责遍历
int L = lessR;
int R = lessR + 1;
int noUsed = 0;
while (L >= 0) {
// 当左侧没人时,退出循环
int solved = 0;
while (R < N && arr[L] + arr[R] <= limit) {
// 计算能够左右组合上船的人数
R++;
solved++;
}
if (solved == 0) {
// 上不去则左移
noUsed++;
L--;
} else {
L = Math.max(-1, L - solved);
}
}
int all = lessR + 1;
int used = all - noUsed;
int moreUnsolved = (N - all) - used;
return used + ((noUsed + 1) >> 1) + moreUnsolved;
}
}