python开发-算法刷题录(持续更新)

文章目录

      • 1.纸币组合
      • 2.指定和求组合集
      • 3.季末日期
      • 4.进制思想
      • 5.旋转数组
      • 6. 开方保留指定小数位

1.纸币组合

  1. 例题
现在有x张十元纸币,y张五元纸币,z张两元纸币,购物后要支付n元(x,y,z,n为整数)。
要求编写一个复杂度为O(1)的函数FindSolution(x,y,z,n),
功能是计算出能否用现在手上拥有的纸币是否足够并能刚好凑齐n元,
而不需要找零。输出一种方案即可结束程序。
  1. 思路
按n为奇还是偶来讨论
1.为偶,则5快的可要可不要(要的话一定是偶数张凑整十);直接看10块的够不够用
	1.1 10块的够用(n//10 < x),不需要五块的(five_num=0);十块的数量可求(因为只要一种凑齐即可,10块按尽可能多给,简单处理:ten_num=n//10);接下来剩下来的个位数交给2块来凑(temp=n-10*ten_num),判断 temp//2的结果与z比较,若z为小,则2块不够,输出false;若z为大或者等,则two_num=temp//2,由此可得该分支下可能凑得齐的结果。
	1.2 10块的不够用,先拿五全部来凑;
	1.2.1 够凑的话拿偶数五凑满整十部分。个位数交给2来凑,同1.1判断,2够个位数就能输出,不够就false
	1.2.2 不够凑的话,拿五块的最多的偶数张凑,剩下的2来凑。2凑完整十接着凑个位数,判断剩下的2是否可凑,不够就false
	(易错点在于补5的时候,一定要控制补偶数张,因此要判断)
2.为奇,则五块必须要,且必须设置为奇数张,因此总数减1*5必为偶数,可以重新调用偶数函数
	先排除特殊情况:
	总数为1,3凑不出;5块为0凑不出;剩下进入正常讨论:
	将num-5得到一个偶数,在次调用为偶的判断函数
	res = func_double(x, y-1, z,num-5)
	if res:
		a, b, c = res
		return a,b+1,c (补上5去奇变偶的那一张)
    else:
    	return false
  1. 解答
def n_double(x, y, z, n):
    if n // 10 <= x:
        ten_num = n // 10
        five_num = 0
        temp = n - ten_num * 10
        if temp // 2 <= z:
            two_num = temp // 2
            return ten_num, five_num, two_num
        else:
            return False
    else:
        ten_num = x
        temp = n - 10 * ten_num
        if temp // 5 <= y:
            if temp // 5 % 2 == 0:
                five_num = temp // 5
            else:
                five_num = temp //5 - 1
        else:
            if y % 2 == 0:
                five_num = y
            else:
                five_num = y - 1
        temp_2 = temp - 5 * five_num
        if temp_2 // 2 <= z:
            two_num = temp_2 // 2
            return ten_num, five_num, two_num
        else:
            return False


def n_single(x, y, z, n):
    if n <= 3 or y < 1:
        return False
    else:
        tempn = n - 5
        if n_double(x, y, z, tempn):
            ten_num, five_num, two_num = n_double(x, y, z, tempn)
            five_num += 1
            return ten_num, five_num, two_num
        else:
            return False


def find_solution(x, y, z, n):
    if n % 2 == 0:
        return n_double(x, y, z, n)
    else:
        return n_single(x, y, z, n)


if __name__ == "__main__":
    print(find_solution(10, 20, 30, 87))
    print(find_solution(6, 20, 30, 81))
    print(find_solution(6, 2, 30, 87))
    print(find_solution(6, 2, 10, 87))

2.指定和求组合集

  1. 题目
给定一个整数数组和指定的数字和,求数组中相加等于指定和的所有子集,如:

输入: array = [2, 3, 7, 4, 10, 8, 6], sum = 10
输出: [2, 8], [3, 7], [4, 6], [10]
  1. 解题思路
    递归:不停把需要考虑的范围一步步缩小 难点:数据格式调通

    后期优化:@functools.lru_cache (节省递归操作时占用空间的工具,需要 import functools)

  2. 解答

import time
import functools


def calsumset(sum_num, lst):
    """
    该函数用来输出给定整数和指定整数数组,指定中数组中元素相加等于指定和的子集的集合
    :param sum_num:指定整数
    :param lst:指定整数数组
    :return:包含所有结果子集的集合
    """
    result = []
    for i, num in enumerate(lst):
        if num == sum_num:
            result.append([num])
            continue
        elif i == len(lst) - 1:
            break
        else:
            tmp_set = calsumset(sum_num - num, lst[i+1:])
            if tmp_set:
                for k in tmp_set:
                    tmp = [num]
                    tmp.extend(k)
                    result.append(tmp)
    return result

@functools.lru_cache(maxsize = 512)
def calsumset2(sum_num, lst):
    """
    该函数用来输出给定整数和指定整数数组,指定中数组中元素相加等于指定和的子集的集合
    :param sum_num:指定整数
    :param lst:指定整数数组
    :return:包含所有结果子集的集合
    """
    result = []
    for i, num in enumerate(lst):
        if num == sum_num:
            result.append([num])
            continue
        elif i == len(lst) - 1:
            break
        else:
            tmp_set = calsumset(sum_num - num, lst[i+1:])
            if tmp_set:
                for k in tmp_set:
                    tmp = [num]
                    tmp.extend(k)
                    result.append(tmp)
    return result


if __name__ == "__main__":
    sum_num = 10
    lst = [1,2 ,3, 4, 5, 6, 7, 9, 10]
    t1 = time.time()
    for i in range(100):
        ret1 = calsumset(sum_num, lst)
    t2 = time.time()
    for i in range(100):
        ret2 = calsumset2(sum_num, tuple(lst))
    t3 = time.time()
    print('origin need time: %s , upgrade need time: %s' % ((t2 - t1), (t3 - t2)) )
    print('输出结果为:', ret2)

3.季末日期

  1. 题目
求当前日期过指定季数之后的季末日期
  1. 解答
import math


def get_quarter_date(date_str, step_len):
    """
    取当前日期过指定季数之后的季末日期
    :param date_str: string, such as "20190212"
    :param step_len: int
    :return: string, like "20200301"
    """
    days_sum = [31, 30, 30, 31]
    if 8 != len(date_str) or not isinstance(step_len, int):
        print("date_str or step_len error")
        return

    year_ori = int(date_str[0:4])
    month_ori = int(date_str[4:6])

    month_temp = step_len*3 + month_ori
    year_new = year_ori + month_temp//12
    month_new = math.ceil(month_temp % 12/3)*3
    day_new = days_sum[month_new // 3 - 1]

    return "%4d%02d%02d" % (year_new, month_new, day_new)


if __name__ == "__main__":
    print(get_quarter_date("20190228", 0))
    print(get_quarter_date("20190228", 1))
    print(get_quarter_date("20190228", 5))
    print(get_quarter_date("20190228", -2))

4.进制思想

import time
import math


def slice_combination(array=[], need_print=False):
    """
    算法思路:利用进制和位数进行巧妙遍历
        数组的行数作为一个整数的位数
        列数作为进制数
        比如10行3列的数组,则组合结果有3的10次方种

        此时遍历0到3^10,根据数值取其对应的位数和第几位的数值即可
        比如数值为12,用三进制表示为110,则对应的数组为【第3行的第1个元素,第二行的第1个元素,第1行的第0个元素】

    :param array: [], 二维数组
    :param need_print: bool, 是否需要输出组合结果
    :return:
    """
    # 因为组合结果可能很大,此处结果不直接返回,改为直接print输出
    if not array or not isinstance(array[0], list):
        return

    row_num = len(array)  # 可以作为位数
    col_num = len(array[0])  # 可以作为进制数
    radix_digit = int(math.pow(col_num, row_num))

    result_num = 0
    for digit in range(radix_digit):
        temp_combination = []
        for i in range(row_num):  # 遍历位数
            temp = digit % col_num  # 获取取第几个数
            temp_combination.append(array[i][temp])
        if need_print:
            print(temp_combination)  # 如果要输出结果,打开慈航注释即可
        result_num += 1

    return result_num


if __name__ == "__main__":
    row_num = 10
    col_num = 3
    a = []
    for i in range(row_num):
        a.append([])
        for j in range(col_num):
            a[i].append(i + 0.1 * j % 1)

    s1 = time.time()
    result_num = slice_combination(a, need_print=False)
    s2 = time.time()
    print("{}行{}列的组合数总共有{}种,耗时{}s".format(row_num, col_num, result_num, s2 - s1))

    row_num = 3
    col_num = 3
    a = []
    for i in range(row_num):
        a.append([])
        for j in range(col_num):
            a[i].append(i + 0.1 * j % 1)
    s1 = time.time()
    result_num = slice_combination(a, need_print=True)
    s2 = time.time()
    print("{}行{}列的组合数总共有{}种,耗时{}s".format(row_num, col_num, result_num, s2 - s1))

5.旋转数组

  1. 题目
写一个函数,传递两个参数(旋转角度, 数组)求出旋转后数组的结果

例如
旋转 : 90°
数组:
	1 	2 	3
	4	5	6
	7	8	9
结果为:
	7	4	1
	8	5	2
	9	6	3

  1. 思路
方法1:通过行列互换+行/列逆序来达到目的,两步操作皆可通过python简单实现

方法2:一个个元素取出来组装的新数组,注意1.第一个元素的位置 2.按行取还是按列取 3.正序还是逆序(这个方法理解起来简单一些)
  1. 解答
import time


def reverse_row_col(ori_data):
    """
    翻转数组的行列,行做列,列做行
    :param ori_data:
    :return: 输出一个对原始数组进行行列转换的新数组
    """
    row_len = len(data)
    col_len = len(data[0])
    new_data = [[_ for _ in range(row_len)] for j in range(col_len)]
    for i in range(row_len):
        for j in range(col_len):
            new_data[i][j] = ori_data[j][i]
    return new_data

def reversed(a = []):
    """
    对列表进行反转,如果参数是宫格(双维列表),则是行上下反转;
    如果参数是宫格中的每行,则是列左右反转;
    :param a: 列表
    :return: 反转后的列表
    """
    return [a[i] for i in range(len(a)-1, -1, -1)]

def rotate_array1(angle, data):
    """
    1.如果为90度,为原数组行列调换+列反转
    2.如果为180度,为原数组行反转+列反转
    3.如果是270度,为原数组行列调换+行反转
    4.如果是360度,为原数组
    :param angle: 旋转角度,整数类型
    :param data: 原数组
    :return: 旋转后数组
    """
    lst = list()
    lst1 = reverse_row_col(data)
    angle = angle % 360
    if 90 == angle:
        for row_dex, row in enumerate(lst1):
            lst.append(reversed(row))
    elif 180 == angle:
        lst2 = reversed(data)
        for row_dex, row in enumerate(lst2):
            lst.append(reversed(row))
    elif 270 == angle:
        lst = reversed(data)
    else:
        lst = data
    return lst


if __name__ == "__main__":
    num = 3
    data = [[j * num + i + 1 for i in range(0, num)] for j in range(0, num)]
    print("旋转90度之后数组为:\n", rotate_array1(90, data))
    print("旋转180度之后数组为:\n", rotate_array1(180, data))
    print("旋转270度之后数组为:\n", rotate_array1(270, data))
    print("旋转360度之后数组为:\n", rotate_array1(360, data))

'''
方法2:取元素法:按旋转的角度理解取元素的方法。注意三点:
1.从哪里取 2.按行取还是按列取 3.顺序取还是逆序取;
具体实现: 略

方法3:机械法:从方法1或者方法2的90度方法多次重复操作;
虽然代码量少,但是效率很低,不推荐
'''

6. 开方保留指定小数位

  1. 题目
某整数开方如何实现,保留指定位数 
  1. 思路
二分逼近,每一个位小到大去试,位从整数到小数一位位从左向右去确认;
如果该位上的数字与前面确定的数字拼接的数字,产生的平方值等于指定整数,该位最终数字即为该数字;
如果一旦大于指定指定整数,则该位最终数字为该数字减1;
  1. 解答
import math


def sqr_digit(digit, bit_num):
    """
    求保留指定位数小数的开方值,时间复杂度O(n),空间复杂度O(1)
    :param digit: number
    :param bit_num: int
    :return: number
    """
    if 0 > digit:
        print("负数没有开方值")
        return

    if 0 > bit_num:
        print("小数保留位数必须为非负数")
        return

    # 先确定整数部分
    int_part = 0
    for i in range(math.ceil(digit)+1):
        if digit == i * i:
            return i
        if digit < i * i:
            int_part = i - 1
            break

    result = int_part

    # 再确定小数部分
    for i in range(1, int(bit_num)+1):
        temp = math.pow(10, -i)
        for j in range(1, 11):
            temp2 = result + temp * j
            if digit == temp2 * temp2:
                return temp2
            if digit < temp2 * temp2:
                result += temp * (j - 1)
                break
    return result


if __name__ == "__main__":
    print(sqr_digit(-23, 2))
    print(sqr_digit(123, 12))
    print(sqr_digit(13, -2))
    print(sqr_digit(16, 6))
    print(sqr_digit(2, 0))

你可能感兴趣的:(数据结构与算法,python,算法)