算法:(2)真题:按照知识点分

真题来源

牛客网-在线编程-全部编程题目,选择知识点

1.1 算法:查找

丰收(关注accumulate, bisect.bisect_left函数)

题目描述

又到了丰收的季节,恰逢小易去牛牛的果园里游玩。
牛牛常说他对整个果园的每个地方都了如指掌,小易不太相信,所以他想考考牛牛。
在果园里有N堆苹果,每堆苹果的数量为ai,小易希望知道从左往右数第x个苹果是属于哪一堆的。
牛牛觉得这个问题太简单,所以希望你来替他回答。
输入描述:
第一行一个数n(1 <= n <= 105)。
第二行n个数ai(1 <= ai <= 1000),表示从左往右数第i堆有多少苹果
第三行一个数m(1 <= m <= 105),表示有m次询问。
第四行m个数qi,表示小易希望知道第qi个苹果属于哪一堆。
输出描述:
m行,第i行输出第qi个苹果属于哪一堆。
示例1
输入
5
2 7 3 4 9
3
1 25 11
输出
1
5
3

代码

import sys
from itertools import accumulate
import bisect

N = int(input())
A = list(map(int, sys.stdin.readline().strip().split()))
M = int(input())
Q = list(map(int, sys.stdin.readline().strip().split()))

a = list(accumulate(A) )
for m in range(M):
    print(bisect.bisect_left(a, Q[m]) +1)

二维数组中的查找(剑指Offer_编程题)

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

代码
从左下角开始向右上方寻找,是向右为变大,向上为变小;这样有分岔路,好查找。跟普通的从左上角往右下找是向右变大向下变大不一样。

另外的想法——先比较每一行的第一位、下一行的第一位和target的大小,以确定行:但是由于不具备单调,不能实现。
[[1,3,5],[2,4,6]] 不能通过3在1、2之间来找。

# -*- coding:utf-8 -*-
class Solution:
    # array 二维列表
    def Find(self, target, array):
        # write code here
        rows = len(array) - 1
        cols = len(array[0]) - 1
        i = rows
        j = 0
        while j <= cols and i >= 0:
            if target < array[i][j]:
                i -= 1
            elif target > array[i][j]:
                j += 1
            else:
                return True
        return False

从1到n整数中1出现的次数(剑指Offer_编程题)

题目描述

求出1 ~ 13的整数中1出现的次数,并算出100 ~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

代码

class Solution:
    def count_1(self,num):
        count1=0
        while(num!=0):
            if num%10==1:
                count1+=1
            num/=10
        return count1
 
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        res=0
        for i in range(n+1):
            res+=self.count_1(i)
        return res

二分查找

题目描述

对于一个有序数组,我们通常采用二分查找的方式来定位某一元素,请编写二分查找的算法,在数组中查找指定元素。
给定一个整数数组A及它的大小n,同时给定要查找的元素val,请返回它在数组中的位置(从0开始),若不存在该元素,返回-1。若该元素出现多次,请返回第一次出现的位置。
测试样例:
[1,3,5,7,9],5,3
返回:1

代码

class BinarySearch:
    def getPos(self, A, n, val):
        left = 0
        right = n-1
        if n <=0:
            return -1
        while(left<=right):
            mid = (left+right)/2
            if A[mid]==val:
                while A[mid] == A[mid-1]:
                    mid = mid-1
                return mid
            if A[mid]<val:
                left = mid+1
            if A[mid]>val:
                right = mid-1
        return -1

1.2 算法:排序

牛牛找工作

题目描述

为了找到自己满意的工作,牛牛收集了每种工作的难度和报酬。牛牛选工作的标准是在难度不超过自身能力值的情况下,牛牛选择报酬最高的工作。在牛牛选定了自己的工作后,牛牛的小伙伴们来找牛牛帮忙选工作,牛牛依然使用自己的标准来帮助小伙伴们。牛牛的小伙伴太多了,于是他只好把这个任务交给了你。

输入描述:
每个输入包含一个测试用例。
每个测试用例的第一行包含两个正整数,分别表示工作的数量N(N<=100000)和小伙伴的数量M(M<=100000)。
接下来的N行每行包含两个正整数,分别表示该项工作的难度Di(Di<=1000000000)和报酬Pi(Pi<=1000000000)。
接下来的一行包含M个正整数,分别表示M个小伙伴的能力值Ai(Ai<=1000000000)。
保证不存在两项工作的报酬相同。
输出描述:
对于每个小伙伴,在单独的一行输出一个正整数表示他能得到的最高报酬。一个工作可以被多个人选择。
示例1
输入
3 3
1 100
10 1000
1000000000 1001
9 10 1000000000
输出
100
1000
1001

代码

import sys
import bisect
lines = sys.stdin.readlines()
n, m = map(int, lines[0].strip().split())
jobs = dict()
for line in lines[1:-1]:
    if not line.strip().split():
        continue
    a, b = map(int, line.strip().split())
    jobs[a] = max(jobs.get(a, 0), b)
skills = map(int, lines[-1].strip().split())
sort_key = sorted(jobs.keys())
# 遍历把能力强但是工资低的换成工资高的选择
for ii in range(1, len(sort_key)):
    if jobs[sort_key[ii]] < jobs[sort_key[ii-1]]:
        jobs[sort_key[ii]] = jobs[sort_key[ii-1]]

for skill in skills:
    if skill in jobs:        # 如果改为 if skill in sort_key: 会时间过长,无法通过
        print(jobs[skill]) 
    else:
        temp = bisect.bisect(sort_key, skill)
        if temp==0:
            print(0)
        else:
            print(jobs[sort_key[temp-1]])

1.3 算法:递归

1.4 算法:贪心

连续子数组最大和(贪心,动归)

题目描述

输入一个整形数组(可能有正数和负数),求数组中连续子数组(最少有一个元素)的最大和。要求时间复杂度为O(n)。

输入描述:

【重要】第一行为数组的长度N(N>=1)
接下来N行,每行一个数,代表数组的N个元素
输出描述:
最大和的结果
示例1
输入
8
1
-2
3
10
-4
7
2
-5
输出
18
说明
最大子数组为 3, 10, -4, 7, 2

代码
认为 d p [ i ] dp[i] dp[i] 是表示为 i i i 为结尾的最大子数组的和。

n = int(input())
l = list()
for i in range(n):
    l.append(int(input()))
#
dp = [0] * n 

for i in range(0,n,1):
    if i==0 or dp[i-1]<=0:
        dp[i] = l[i]
    elif i>0 and dp[i-1]>0:
        dp[i] = dp[i-1]+l[i]

print(max(dp))

划分数组,让最大的子数组最小

题目描述

shopee的零食柜,有着各式各样的零食,但是因为贪吃,小虾同学体重日益增加,终于被人叫为小胖了,他终于下定决心减肥了,他决定每天晚上去操场跑两圈,但是跑步太累人了,他想转移注意力,忘记痛苦,正在听着音乐的他,突然有个想法,他想跟着音乐的节奏来跑步,音乐有7种音符,对应的是1到7,那么他对应的步长就可以是1-7分米,这样的话他就可以转移注意力了,但是他想保持自己跑步的速度,在规定时间m分钟跑完。为了避免被累死,他需要规划他每分钟需要跑过的音符,这些音符的步长总和要尽量小。下面是小虾同学听的歌曲的音符,以及规定的时间,你能告诉他每分钟他应该跑多少步长?

输入描述:

输入的第一行输入 n(1 ≤ n ≤ 1000000,表示音符数),m(1<=m< 1000000, m <= n)组成,
第二行有 n 个数,表示每个音符(1<= f <= 7)
输出描述:
输出每分钟应该跑的步长
示例1
输入
8 5
6 5 6 7 6 6 3 1
输出
11
说明
6 | 5 6 | 7 | 6 | 6 3 1 为最优解
如果小于11,必然分段大于5

代码

import sys
line1 = list(map(int, sys.stdin.readline().strip().split() ))
line2 = list(map(int, sys.stdin.readline().strip().split() ))
n,m = line1
SUM = sum(line2)
MAX = max(line2)
left = MAX
right = SUM
while(left <= right):
	mid = (left + right) // 2
	count = 0
	tmp = 0
	for i in range(n):
		tmp += line2[i]
		if tmp > mid:
			count += 1
			tmp = line2[i]
	if count + 1 <= m:
		right = mid - 1 
	else:
		left = mid + 1
	
print(mid)
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n, m;
    cin >> n >> m;
    int x, left = 0, right = 0, mid;
    vector<int> v;
    while(cin >> x){
        right += x; //全部数加和
        left = max(left, x); // 取最大的数
        v.push_back(x);
    }
    while(left <= right){
        mid = left + (right - left) / 2; 
        int count = 0, temp = 0;
        for(int i = 0; i < v.size(); i++){
            temp += v[i]; // 
            if(temp > mid){
                count++; // 只要再temp刚才加v[i]超过了mid,则计数加1
                temp = v[i];
            }
        }
        count+1 <= m ? right = mid-1 : left = mid + 1;
    }
    cout << mid << endl;
    return 0;
}

1.5 算法:动态规划

回文数组(数组、动归)(搜狐)

题目描述

对于一个给定的正整数组成的数组 a[] ,如果将 a 倒序后数字的排列与 a 完全相同,我们称这个数组为“回文”的。
例如, [1, 2, 3, 2, 1] 的倒序是他自己,所以是一个回文的数组;而 [1, 2, 3, 1, 2] 的倒序是 [2, 1, 3, 2, 1] ,所以不是一个回文的数组。
对于任意一个正整数数组,如果我们向其中某些特定的位置插入一些正整数,那么我们总是能构造出一个回文的数组。
输入一个正整数组成的数组,要求你插入一些数字,使其变为回文的数组,且数组中所有数字的和尽可能小。输出这个插入后数组中元素的和。
例如,对于数组 [1, 2, 3, 1, 2] 我们可以插入两个 1 将其变为回文的数组 [1, 2, 1, 3, 1, 2, 1] ,这种变换方式数组的总和最小,为 11 ,所以输出为 11 。

输入描述:
输入数据由两行组成: 第一行包含一个正整数 L ,表示数组 a 的长度。 第二行包含 L 个正整数,表示数组 a 。 对于 40% 的数据: 1 < L <= 100 达成条件时需要插入的数字数量不多于 2 个。 对于 100% 的数据: 1 < L <= 1,000 0 < a[i] <= 1,000,000 达成条件时需要插入的数字数量没有限制。
输出描述:
输出一个整数,表示通过插入若干个正整数使数组 a 回文后,数组 a 的数字和的最小值。
示例1
输入
8
51 23 52 97 97 76 23 51
输出
598

代码
问题可以转换为求回文子序列的最大和,则最终最优解为2 * sum - dp[0][a.length - 1],sum为数组a所有元素的和。
参考

/**
 * Dynamic Programming
 *
 * State:
 *   dp[i][j]: 表示a[i],...,a[j]中的回文子序列的最大和
 *
 * Initial State:
 *   dp[i][i] = a[i]
 *
 * State Transition:
 *   if (a[i] == a[j]) dp[i][j] = dp[i + 1][j - 1] + 2 * a[i]; 中间一段加上左右
 *   else dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 取中间加右端或者左端
 *
 * @author wylu
 */
import sys
n = int(input())
lines = sys.stdin.readline().strip().split()
values = list(map(int, lines))
dp = [[0]*n for _ in range(n)]
s = 0

for i in range(n-1, -1, -1):
    dp[i][i] = values[i]
    s += values[i]
    if i == n-1:
        continue
    for j in range(i+1, n):
        if values[i] == values[j]:
            dp[i][j] = dp[i+1][j-1] + values[i] * 2 
        else:
            dp[i][j] = max(dp[i+1][j], dp[i][j-1])
#
print(s*2 - dp[0][n-1])

连续

未归类

中位数

题目描述
小M给你一个长度为n的数组,我们定义median数为该数组从小到大排序后,下标为(n-1)/2的数字。下标从0开始,(n-1)/2表示整数除法,即向下取整。现在我们已经得到了一个初始的数组,我们希望这个数组的median数是一个给定数字x。所以我们需要加入一些数到数组中从而完成我们的目标。数组中的元素可以重复,请问,最少需要加入多少个数字才能达成这个目标。

输入描述:
第一行输入两个整数n x (1 <= n <= 500, 1 <= x <= 10^5)。
接下来一行有n个正整数表示初始的数组,用空格分开,范围是[1, 10^5]。
输出描述:
输出需要加入最少的元素个数才能够使得新数组的median数成为x。
示例1
输入
3 2
2 3 4
输出
1
说明
样例一中,加入1,得到1 2 3 4,那么median数的下标为(4 - 1)/2 = 1, median数为2。加一个数字就可以了。
示例2
输入
3 4
1 2 3
输出
4
说明
样例二中,加入4 5 6 7,得到1 2 3 4 5 6 7,median数为4。最少加4个数字。

代码

import sys
l1 = list(map(int, sys.stdin.readline().strip().split() ))
n, x = l1
l2 = list(map(int, sys.stdin.readline().strip().split() ))
e, r, l = 0,0,0

for i in range(len(l2)):
    if l2[i]==x:
        e += 1
    elif l2[i] < x:
        l += 1
    else:
        r += 1
#
m = (n-1)/2
if l <= m: # 给定值左边的个数 小于 给定值的位置;即左边的个数不够 
    if r < n-m: # 给定值右边的个数不够
        print(0) # 数组中,给定值左边的不够,右边的也不够,所以中间的一些值都是给定值;故不需要添加数字
    else: # 右边的个数多,所以左边添加个数 
        print(r - l - e) # 因为需要左边添加个数,所以将给定值当做左边的。这样可以尽可能少的添加 
else: # 左边的个数多 
    print(l-r-e+1) # 需要添加右边的,所以将等于给定值的数当做右边的;同时由于m的位置是向下取整,因此右边需要多加1 

统一字符串出现次数

通过键盘输入一串小写字母(a~z)组成的字符串。
请编写一个字符串归一化程序,统计字符串中相同字符出现的次数,并按字典序输出字符及其出现次数。
例如字符串"babcc"归一化后为"a1b2c2"

import sys
line1 = [each for each in sys.stdin.readline().strip()]
num = len(set(line1))

def rank(alist_raw):
    alist = alist_raw.copy()
    if len(alist) >= 2:
        mid = alist[0]
        left = []
        right = []
        alist.remove(mid)
        for i in alist:
            if i <= mid:
                left.append(i)
            else:
                right.append(i)
        return rank(left) + [mid] + rank(right)
    else:
        return alist
        
def statistic(alist_raw):
    alist = alist_raw.copy()
    num = 1
    num_list = []
    set_list = [alist[0]]
    for i in range(1,len(alist)):
        if alist[i]==alist[i-1]:
            num += 1
        else:
            num_list.append(num)
            set_list.append(alist[i])
            num = 1
    num_list.append(num)
    return set_list, num_list
        
rank_alist = rank(line1)
result = []
set_list, num_list = statistic(rank_alist)
for i in range(num):
    result.append(set_list[i])
    result.append( num_list[i])
print("".join(str(i)for i in result))

题目2:找出数组中唯一一个出现奇数次的数字

题目——原型

由于任何两个相同的数(偶数次),异或处理之后,都为0;
0和任何数异或,都得到另外这个数;
加入不同的数字(奇数次)后,异或处理为本身。

代码

# 找出数组中唯一一个出现奇数次的数字
import sys
n = int(input())
line1 = [int(each) for each in sys.stdin.readline().strip().split()]

result = 0
line1_copy = line1.copy()

for i in range(1,len(line1)):
    line1_copy[0] = line1_copy[0] ^ line1_copy[i]
print(line1_copy[0])

变形:找出数组中唯二出现奇数次的数字

一趟异或下来,最终的结果即为那两个奇数异或的结果;
如果其结果不为0,则说明至少存在这两个数中的某一个数的某一位不全为0
那么可以根据这个不同的位,将数组分成两个部分,再对这两个独立的部分,两天异或,就可以得到这两个奇数了

记得:取反加一得到补码(相反数)

# 找出数组中唯一一个出现奇数次的数字
##import sys
##n = int(input())
##line1 = [int(each) for each in sys.stdin.readline().strip().split()]

def findOddNum(line1_copy):
    for i in range(1,len(line1_copy)):
        line1_copy[0] = line1_copy[0] ^ line1_copy[i]
    print(line1_copy[0])

def findOddTwiceNum(line2):
    oneResult = 0
    result = 0
    for i in range(len(line2)):
        result = result ^ line2[i]
    oneRight = result & (~result + 1)
    # 一个数和自己的相反数进行位与,得到的是原数中右起第一个为1,其余都为0的数
    # 因为右起第一个为1的位,右边都是0(或者没有位),那么相反数的对应原数
    # 0的位置,都是1。只要加1,就能够进位。一直到右起第一个1.
    for i in range(len(line2)):
        if line2[i] & oneRight != 0:
            oneResult ^= line2[i]
    # 由于相乘,只要符合条件的偶数次的数,肯定异或为0,忽略。剩下两个奇数次的数AB,
    # 其中必定有一个A是和result有同一个最右起的第一个1。求与之后A这个数,
    # 表现为非0,和之前的偶数次数求异或,得到A这个数本身。
    # A是oneResult,另外一个B用result异或可得。
    print(str(oneResult)  + ' and ' + str(oneResult ^ result) )

line1 = [3,4,6,3,5,6,6,4,6,5,6] # 6
line1_copy = line1.copy()
findOddNum(line1_copy)

line2 = [3,3,5,6,5,4,7,7,4,4] # 4 and 6
findOddTwiceNum(line2)

找出数组中唯一一个出现偶数次的数字

通过删减替将word1改成word2的最小次数

参考

def minDistance(word1, word2):
    n1 = len(word1)
    n2 = len(word2)
    dp = []
    for i in range(len(word1)+1):
        dp.append(i)

    for j in range(1,n2+1):
        prev = dp[0]
        dp[0] = j
        for i in range(1,n1+1):
            temp = dp[i]
            if word1[i-1] == word2[j-1]:
                dp[i] = prev
            else:
                dp[i] = 1 + min(prev, min(dp[i], dp[i-1]))
            prev = temp

    return dp[n1]

import sys
word1 = sys.stdin.readline().strip()
word2 = sys.stdin.readline().strip()
print(minDistance(word1,word2))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值