一、自定义排序
当年用C++写了十几遍这种题,结果笔试的时候还是没有写出来,唉!问题出在只判断姓名字典序的时候,x.name[i] < y.name[i] return了-1,但是x.name[i] > y.name[i]的时候没有return.
var height = '176 176 176 176 170';
var names = 'beta alpha bamma a a';
height = height.split(' ');
names = names.split(' ');
var n = height.length;
var arr = [];
for(let i = 0; i < n; i++){
arr.push({height: parseInt(height[i]), name: names[i]});
}
var res = arr.sort(cmp);
function cmp(x, y){
if(x.height < y.height) return -1;
else if(x.height > y.height) return 1;
else if(x.height == y.height){
var lena = x.name.length;
var lenb = y.name.length;
var minn = Math.min(lena, lenb);
for(let i = 0; i < minn; i++){
if(x.name[i] < y.name[i]){
return -1;
}else if(x.name[i] > y.name[i]){
return 1;
}
}
if(lena < lenb) return -1;
else return 1;
}
}
最近看别人的博客,说是如果同时要对很多字段进行排序的话,代码会很丑陋,现在都将某个字段作为输入,进行先后排序。
function createCompareFunction(fieldName){
return function(object1,object2){
var value1=object1[fieldName];
var value2=object2[fieldName];
if(value1<value2){
return -1;
}else if(value1==value2){
return 0;
}else{
return 1;
}
};
}
studentsData.sort(createCompareFunction("name"));//按照name字段进行排序
studentsData.sort(createCompareFunction("age"));//按照age字段进行排序
二、排列组合
怎么回事呢?高中学的排列组合都忘到肚子里去了?
题目描述:给出一道数组,然后求出长度为k的和为偶数子序列个数。比如说数组为[1,2,3,4,5], 目标k = 2,子序列有 [1,3] [1, 5] [2, 4], [3, 5]。(子序列的定义就是可以不连续)
题目思考1:
- 一开始以为是连续的,在求合法的子串,想到可以用前缀和来判断,每次遍历,用
a[i+k]-a[i]
,如果是偶数则+1。在leetcode上有类似的题:
1524. 和为奇数的子数组数目 :这道题的前缀和是用来维护"前面为奇数和为偶数数组的个数",在判断该数组有多少个为奇数的连续子数组时,如果当前的数为奇数则结果=+前面有多少个偶数,如果当前的数是偶数则结果=+前面的奇数个数. 因为奇数=奇数+偶数,也=偶数+奇数
- 但后来同学提醒,子序列是不连续的。思考了一下,或许可以用排列组合来写,找出数组中奇数和偶数的个数,然后偶数子序列只有一种情况:选偶数
x
个奇数(0, 2, 4…)以及(k-x)
个偶数。 - 假设子序列的长度为k,设数组中奇数为a个,偶数为b个,此时进行排列组合:
- 当
x=0
,选0
个奇数,k
个偶数,个数为 C b k C^k_b Cbk - 当
x=2
,选2
个奇数,k-2
个偶数,个数为 C a 2 C b k − 2 C^2_aC^{k-2}_b Ca2Cbk−2 - 当
x=4
,选4
个奇数,k-4
个偶数,个数为 C a 4 C b k − 4 C^4_aC^{k-4}_b Ca4Cbk−4 - …
- (k为偶数) 当
x=k
,选k
个偶数,0
个偶数,个数为 C a k C^k_a Cak - (k为奇数) 前面几项的和
- 当
- 用排列组合写可能会出现大数阶层的问题,比如说当b=1000的时候,需要计算 C 1000 x C^x_{1000} C1000x,int存不下
0 ! = 1 0!=1 0!=1
P n m = n ! ( n − m ) ! P^m_n= \frac {n!} {(n-m)!} Pnm=(n−m)!n! 表示排列(permutation)
,需要保证顺序
C n m = n ! m ! ( n − m ) ! C^m_n= \frac{n!} {m!(n-m)!} Cnm=m!(n−m)!n! 表示组合(combination)
C n m = C n n − m C^m_n=C^{n-m}_n Cnm=Cnn−m
C n 0 + C n 1 + . . . + C n n = 2 n C^0_n+C^1_n+...+C^n_n=2^n Cn0+Cn1+...+Cnn=2n
C n 0 + C n 2 + C n 4 + . . . = C n 1 + C n 3 + C n 5 + . . . = 2 n − 1 C^0_n+C^2_n+C^4_n+...=C^1_n+C^3_n+C^5_n+...=2^{n-1} Cn0+Cn2+Cn4+...=Cn1+Cn3+Cn5+...=2n−1
var k = 3; //子序列的长度为3
var arr = [1, 4, 2, 2];
var odd = 0, even = 0;
for(let i = 0; i < arr.length; i++){
if(arr[i]%2==0) even++;
else odd++;
}//统计数组中奇数odd和偶数even的个数
var res = 0;
for(let i = 0; i <= k; i+=2){
res+=c(odd, i)*c(even, k-i);//只能选偶数(i)个奇数,其他的均为奇数(k-i)
}
function c(n, m){//排列组合中Cnm=[n!]/[m!(n-m)!]=[n乘到...(n-m-1)]/m乘到...1
let ans = 1;
for(let i = n; i > n-m; i--){
ans = ans*i;
}
for(let i = 1; i <= m; i++){
ans = ans/i;
}
return ans;
}
console.log(res);
题目思考2:
- 其实这道题还可以用动态规划做,如果用动态规划做的话,就不需要考虑大数阶层的问题了,可以一边相加一遍模,但大数阶层不能一遍相乘一遍模。
- 判断当前arr[i]是偶数还是奇数:
- 如果是偶数,则子序列为偶数和有两种情况:(1)这个数为子序列的一部分,则需要加上子序列长度为k-1的偶数和(2)认为这个数不是子序列的一部分,则选择子序列长度为k的偶数和。
- 如果是奇数,则子序列为偶数和有两种情况:(1)这个数为子序列的一部分,则需要加上子序列长度为k-1的奇数和(2)认为这个数不是子序列的一部分,则选择子序列长度为k的偶数和。
先放下代码,之后再来看。https://leetcode-cn.com/circle/discuss/dzroTG/
def func(nums, n) -> int:
# dp0[i][j]表示前i个数中长度为j的子序列和为偶数的个数
# dp1[i][j]表示前i个数中长度为j的子序列和为奇数的个数
# 递推公式
# 如果当前值是偶数,那么dp0[i][j]就分为选择该数和不选该数两种情况累计,以下同理
# dp0[i][j] = dp0[i-1][j-1] + dp0[i-1][j]
# dp1[i][j] = dp1[i-1][j-1] + dp1[i-1][j]
# 如果当前值是奇数,那么
# dp0[i][j] = dp1[i-1][j-1] + dp0[i-1][j]
# dp1[i][j] = dp0[i-1][j-1] + dp1[i-1][j]
m = len(nums)
dp0 = [[0] * (n + 1) for _ in range(m + 1)]
dp1 = [[0] * (n + 1) for _ in range(m + 1)]
# 注意,认为长度为0的子序列和为0,所以为偶数,所以dp0[i][0] = 1
for i in range(m + 1):
dp0[i][0] = 1
for i in range(1, m + 1):
if nums[i-1] % 2 == 0:
for j in range(1, min(i, n) + 1):
dp0[i][j] = dp0[i-1][j-1] + dp0[i-1][j]
dp1[i][j] = dp1[i-1][j-1] + dp1[i-1][j]
else:
for j in range(1, min(i, n) + 1):
dp0[i][j] = dp1[i-1][j-1] + dp0[i-1][j]
dp1[i][j] = dp0[i-1][j-1] + dp1[i-1][j]
return dp0[m][n]
三、深度优先搜索
我总共就参加了两次笔试,深搜就考了两题,一题是我最以前最喜欢的拓扑排序(估计是题目读的有问题,也是因为很就没有练习了,最后输出错了),没写出来。一题是看上去很难的深搜。
- 拓扑排序在leetcode上有类似的题:207.课程表,写了一下,思路是没有问题的,有问题的是我。
var canFinish = function(numCourses, prerequisites) {
var indegree = new Array(numCourses).fill(0);
var visited = new Array(numCourses).fill(0);
var graph = Array.from({length: numCourses}, x=>[]);
for(let i = 0; i < prerequisites.length; i++){
var from = prerequisites[i][1], to = prerequisites[i][0];
graph[from].push(to);
indegree[to]++;
}
var count = 0;
var res = new Array();
var q = new Array();
for(let i = 0; i < numCourses; i++){
if(indegree[i] == 0) {
q.push(i);
visited[i] = 1;
}
}
while(q.length){
let node = q.shift();
res[count++] = node;
for(const next of graph[node]){
if(indegree[next] > 0) indegree[next]--;
if(indegree[next] == 0 && !visited[next]){
visited[next] = 1;
q.push(next);
}
}
}
if(res.length == numCourses)
return true;
else return false;
};
四、动态规划
题目描述:有一个数组串,想取一个子序列(可不连续),使得该子序列是9的倍数。一共能够取多少个子序列?(前导零合法),若两个子序列在原串中的位置不同,则认为它们不同。答案对10^9+7取模。
输入:1188
输出:5
问题思考:
- 解这道题主要根据这个提示:判断一个数是否是9的倍数:每个位置的数相加之和能整除9,就是9的倍数。比如,8811,8+8+1+1=18,18能整除9,所以8811是9的倍数。
- 比如说当前的数为8,有两种情况
- 不选择8作为子序列的一部分:那么就需要找前i-1个序列所选择的值排列组合的和为9的个数
- 选择8作为子序列的一部分:那么就需要找前i个序列所选择的值排列组合的和为1的个数
- 所以dp[i][j]的含义表示为:前i个数排列组合的和 mod 9刚好= j 情况的个数;
a[0] = 1 | a[1] = 1 | a[2] = 8 | a[3] = 8 | |
---|---|---|---|---|
idx = 0 | idx = 1 | idx = 2 | idx = 3 | |
0 | 0 | 0+[8, 0] (0)=0 | 0+[1,1] (2)=2 | 2+[1,2] (3)=5 |
1 | 1 | 1+[1, 0] (0)=2 | 2+ [2,1] (1)=3 | 3+[2,2] (1)=4 |
2 | 0 | 0+[1,0] (1)=1 | 1+[3,0] (0)=1 | 1+[3,2] (0)=1 |
3 | 0 | 0… | 0… | 0… |
4 | 0 | 0… | 0… | 0… |
5 | 0 | 0… | 0… | 0… |
6 | 0 | 0… | 0… | 0… |
7 | 0 | 0… | 0… | 0… |
8 | 0 | 0… | 0… | 0… |
五、确定目标状态、分类讨论
题目说明:给出一个字符串,只有0和1,每次可交换两个相邻的两个数,得到任何0和1不相邻的情况的最小交换次数。
输入:11100
输出:3
var a = "111000";
var n = a.length;
var n0 = 0, n1 = 0;//0和1的个数
for(let i = 0; i < a.length; i++){
if(a[i] == 0) n0++;
else n1++;
}
var cul = 0;
var cul1 = 0, cul2 = 0;
if(n%2 == 1){//字符串为奇数时,可能为010、101
if(n0 > n1){//1出现在奇数位,类似于010
var j = 0;
for(let i = 1; i < n; i+=2){
while(a[j] != 1) j++;
cul += Math.abs(j-i);
j++;
}
}else if(n0 < n1){//1出现为偶数位,类似于101
var j = 0;
for(let i = 0; i < n; i+=2){
while(a[j] != 1) j++;
cul += Math.abs(j-i);
j++;
}
}
console.log(cul);
}else if(n%2 == 0){
var j = 0;
for(let i = 1; i < n; i+=2){ //1出现在在奇数位,类似于0101
while(a[j] != 1) j++;
cul1 += Math.abs(j-i);
j++;
}
var j = 0;
for(let i = 0; i < n; i+=2){ //1出现在在偶数位,类似于1010
while(a[j] != 1) j++;
cul2 += Math.abs(j-i);
j++;
}
console.log(Math.min(cul1, cul2));//比较两种情况取最小值
} //整体思路:进行分类后,目标序列是确定的,所以每次将序列中的1移动到目标序列中,就能得到解