【笔试算法】本科学的算法全都还给老师了

一、自定义排序

当年用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

  1. 一开始以为是连续的,在求合法的子串,想到可以用前缀和来判断,每次遍历,用a[i+k]-a[i],如果是偶数则+1。在leetcode上有类似的题:

1524. 和为奇数的子数组数目 :这道题的前缀和是用来维护"前面为奇数和为偶数数组的个数",在判断该数组有多少个为奇数的连续子数组时,如果当前的数为奇数则结果=+前面有多少个偶数,如果当前的数是偶数则结果=+前面的奇数个数. 因为奇数=奇数+偶数,也=偶数+奇数

  1. 但后来同学提醒,子序列是不连续的。思考了一下,或许可以用排列组合来写,找出数组中奇数和偶数的个数,然后偶数子序列只有一种情况:选偶数x个奇数(0, 2, 4…)以及(k-x)个偶数。
  2. 假设子序列的长度为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 Ca2Cbk2
    • x=4,选4个奇数,k-4个偶数,个数为 C a 4 C b k − 4 C^4_aC^{k-4}_b Ca4Cbk4
    • (k为偶数) 当x=k,选k个偶数,0个偶数,个数为 C a k C^k_a Cak
    • (k为奇数) 前面几项的和
  3. 用排列组合写可能会出现大数阶层的问题,比如说当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=(nm)!n! 表示排列(permutation),需要保证顺序
C n m = n ! m ! ( n − m ) ! C^m_n= \frac{n!} {m!(n-m)!} Cnm=m!(nm)!n! 表示组合(combination)
C n m = C n n − m C^m_n=C^{n-m}_n Cnm=Cnnm
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+...=2n1

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

  1. 其实这道题还可以用动态规划做,如果用动态规划做的话,就不需要考虑大数阶层的问题了,可以一边相加一遍模,但大数阶层不能一遍相乘一遍模。
  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]

三、深度优先搜索

我总共就参加了两次笔试,深搜就考了两题,一题是我最以前最喜欢的拓扑排序(估计是题目读的有问题,也是因为很就没有练习了,最后输出错了),没写出来。一题是看上去很难的深搜。

  1. 拓扑排序在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

问题思考

  1. 解这道题主要根据这个提示:判断一个数是否是9的倍数:每个位置的数相加之和能整除9,就是9的倍数。比如,8811,8+8+1+1=18,18能整除9,所以8811是9的倍数。
  2. 比如说当前的数为8,有两种情况
    • 不选择8作为子序列的一部分:那么就需要找前i-1个序列所选择的值排列组合的和为9的个数
    • 选择8作为子序列的一部分:那么就需要找前i个序列所选择的值排列组合的和为1的个数
  3. 所以dp[i][j]的含义表示为:前i个数排列组合的和 mod 9刚好= j 情况的个数;
a[0] = 1a[1] = 1a[2] = 8a[3] = 8
idx = 0idx = 1idx = 2idx = 3
000+[8, 0] (0)=00+[1,1] (2)=22+[1,2] (3)=5
111+[1, 0] (0)=22+ [2,1] (1)=33+[2,2] (1)=4
200+[1,0] (1)=11+[3,0] (0)=11+[3,2] (0)=1
300…0…0…
400…0…0…
500…0…0…
600…0…0…
700…0…0…
800…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移动到目标序列中,就能得到解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值