《算法设计与分析》专题报告
专题名称:蛮力算法的设计与分析
专业:计算机类
班级:22级计算机类二班
学号:XXX
姓名:XXX
指导教师:XX
实验成绩(等级):
日期: 2022 年 9 月 10 日
1、专题名称
蛮力算法的设计与分析
2、实践内容
2.1 实践内容 1
(1)题目要求
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
示例 2:
输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]] 解释: 1 + 2 + 6 = 9 1 + 3 + 5 = 9 2 + 3 + 4 = 9 没有其他符合的组合了。
示例 3:
输入: k = 4, n = 1 输出: [] 解释: 不存在有效的组合。 在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
提示:
2 <= k <= 9
1 <= n <= 60
(2)算法设计
由题可得,列表不能包含相同的组合两次,若默认k个数由小到大排序,可以减轻计算量。
即问题可转化为k个数的数组之和等于n
其中应满足:
sum[i] < sum[i+1]
sum[i] <= 9
然后再暴力回溯即可
(3)代码实现
class Solution { public: /* sum:数组 now:现在是第几个数 k:一共有几个数 n:k个数之和 */ vector<vector<int>> result; // 总结果集 void dfs(int *sum,int now,int k,int n) { for(int i = sum[now-1]+1 ; i <= 9 ; i++) { sum[now] = i; if(now == k-1) // 如果数量足够 { int sum_num = 0; // 变量 for(int p = 0 ; p < k ; p++) // 累加数据 sum_num += sum[p]; if(sum_num == n) // 检验输出 { vector<int> short_result; // 单条式子 for(int p = 0 ; p < k ; p++) { short_result.push_back(sum[p]); } result.push_back(short_result); break; // 此时最后一个数字大小已确定,可以直接退出循环 } continue; } dfs(sum,now+1,k,n); } } vector<vector<int>> combinationSum3(int k, int n) { int sum[9] = {}; // 总和数组 for(int i = 1 ; i <= 9-k+1 ; i++) { memset(sum,0,sizeof(sum)); // 重置sum数组 sum[0] = i; dfs(sum,1,k,n); } return result; } };
(4)算法分析
时间复杂度:O(n^2)
空间复杂度:O(n)
算法优点:实现简单。
缺点:时间复杂度较高。
2.2 实践内容 2
(1)题目要求
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2] 输出:[2,1]
示例 3:
输入:head = [] 输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
(2)算法设计
使用三指针,顺序遍历链表即可,注意对特殊情况的特殊解决方法。
(3)代码实现
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* reverseList(ListNode* head) { if(head == NULL) return head; ListNode* pnow = head; // 现结点指针 ListNode* pfront = pnow->next; // 前结点指针 ListNode* pback = pnow; // 后结点指针 pnow = pnow->next; while(pfront != NULL) { pfront = pfront->next; // 前结点往后移一位 pnow->next = pback; // 现结点的后继结点改为后结点 pback = pnow; // 后结点往后移一位 pnow = pfront; // 现结点往后移一位 } head->next = NULL; // 反转链表后原来的头结点的后继结点为空 head = pback; // 反转链表后的头结点 return head; } };
(4)算法分析
时间复杂度:O(n)
空间复杂度:O(1)
算法优点:空间复杂度低。
缺点:稍微难想一点
2.3 实践内容 3
(1)题目要求
给你一份『词汇表』(字符串数组) words
和一张『字母表』(字符串) chars
。
假如你可以用 chars
中的『字母』(字符)拼写出 words
中的某个『单词』(字符串),那么我们就认为你掌握了这个单词。
注意:每次拼写(指拼写词汇表中的一个单词)时,chars
中的每个字母都只能用一次。
返回词汇表 words
中你掌握的所有单词的 长度之和。
示例 1:
输入:words = ["cat","bt","hat","tree"], chars = "atach" 输出:6 解释: 可以形成字符串 "cat" 和 "hat",所以答案是 3 + 3 = 6。
示例 2:
输入:words = ["hello","world","leetcode"], chars = "welldonehoneyr" 输出:10 解释: 可以形成字符串 "hello" 和 "world",所以答案是 5 + 5 = 10。
提示:
1 <= words.length <= 1000
1 <= words[i].length, chars.length <= 100
- 所有字符串中都仅包含小写英文字母
(2)算法设计
1、用两个数组分别计算字母表和单词的单个字母数量
2、遍历词汇表,判断单词的每个字母数量是否小于字母表的每个字母数量,若为真,则计算单词的长度
3、最后输出总单词长度
(3)代码实现
class Solution { public: int countCharacters(vector<string>& words, string chars) { int length_sum = 0; // 输出长度 int letter_sum[26] = {}; // 字母表 for(int i = 0 ; i < chars.length() ; i++) letter_sum[chars[i] - 'a']++; // 字母表字母数量计算 for(int i = 0 ; i < words.size() ; i++) { string str = words[i]; int letter_sum2[26] = {}; // 临时字母表 for(int j = 0 ; j < str.size() ; j++) letter_sum2[str[j] - 'a']++; // 临时字母表字母数量计算 int num_sum = 0; // 单个单词长度 for(int j = 0 ; j < 26 ; j++) { if(letter_sum2[j] > letter_sum[j]) // 如果不符合条件 break; if(j == 25) { for(int k = 0 ; k < 26 ; k++) { while(letter_sum2[k]) // 单词长度计算 { letter_sum2[k]--; num_sum++; } } length_sum += num_sum; } } } return length_sum; } };
(4)算法分析
时间复杂度:O(n)
空间复杂度:O(1)
算法优点:实现简单。
缺点:时间复杂度一般,空间复杂度固定(开两个int类型的长度26的数组),对n小的情况会造成内存浪费。
2.4 实践内容 4
(1)题目要求
给你一个数组 prices
,其中 prices[i]
是商店里第 i
件商品的价格。
商店里正在进行促销活动,如果你要买第 i
件商品,那么你可以得到与 prices[j]
相等的折扣,其中 j
是满足 j > i
且 prices[j] <= prices[i]
的 最小下标 ,如果没有满足条件的 j
,你将没有任何折扣。
请你返回一个数组,数组中第 i
个元素是折扣后你购买商品 i
最终需要支付的价格。
示例 1:
输入:prices = [8,4,6,2,3] 输出:[4,2,4,2,3] 解释: 商品 0 的价格为 price[0]=8 ,你将得到 prices[1]=4 的折扣,所以最终价格为 8 - 4 = 4 。 商品 1 的价格为 price[1]=4 ,你将得到 prices[3]=2 的折扣,所以最终价格为 4 - 2 = 2 。 商品 2 的价格为 price[2]=6 ,你将得到 prices[3]=2 的折扣,所以最终价格为 6 - 2 = 4 。 商品 3 和 4 都没有折扣。
示例 2:
输入:prices = [1,2,3,4,5] 输出:[1,2,3,4,5] 解释:在这个例子中,所有商品都没有折扣。
示例 3:
输入:prices = [10,1,1,6] 输出:[9,0,1,6]
提示:
1 <= prices.length <= 500
1 <= prices[i] <= 10^3
(2)算法设计
1、开一个大数组
2、双层for循环,找出符合题目条件的折扣
3、for循环算出最终返回数组
(3)代码实现
class Solution { public: vector<int> finalPrices(vector<int>& prices) { int price_real[500] = {} ; for(int i = 0 ; i < prices.size() ; i++ ) { for(int j = i+1 ; j < prices.size() ; j++ ) { if(prices[j] <= prices[i]) { price_real[i] = prices[j]; break; } } } for(int i = 0 ; i < prices.size() ; i++ ) prices[i] -= price_real[i] ; return prices; } };
(4)算法分析
时间复杂度:O(n^2)
空间复杂度:O(1)
算法优点:算法易想,实现简单。
缺点:时间复杂度较高,空间复杂度固定(开int类型的长度500的数组),对n小的情况会造成内存浪费。
2.5 实践内容 5
(1)题目要求
超市正在促销,你可以用 numExchange
个空水瓶从超市兑换一瓶水。最开始,你一共购入了 numBottles
瓶水。
如果喝掉了水瓶中的水,那么水瓶就会变成空的。
给你两个整数 numBottles
和 numExchange
,返回你 最多 可以喝到多少瓶水。
示例 1:
输入:numBottles = 9, numExchange = 3 输出:13 解释:你可以用 3 个空瓶兑换 1 瓶水。 所以最多能喝到 9 + 3 + 1 = 13 瓶水。
示例 2:
输入:numBottles = 15, numExchange = 4 输出:19 解释:你可以用 4 个空瓶兑换 1 瓶水。 所以最多能喝到 15 + 3 + 1 = 19 瓶水。
提示:
1 <= numBottles <= 100
2 <= numExchange <= 100
(2)算法设计
while循环对numBottles自减,减够numExchange时再+1即可,同时计算所有喝到的水的瓶数
(3)代码实现
class Solution { public: int numWaterBottles(int numBottles, int numExchange) { int drink = 0; int total = 0; while(numBottles) { numBottles --; drink ++; total ++; if(total == numExchange) { numBottles ++; total = 0; } } return drink; } };
(4)算法分析
时间复杂度:O(n)
空间复杂度:O(1)
算法优点:算法易想,实现简单。
缺点:时间复杂度还能进一步优化。
3、问题与总结
问题:第一、第二道题花费时间较长,对力扣的提交模式不熟悉,对C++的容器不熟悉,算法的时间复杂度和空间复杂度不够低。
总结:成功完成所有题目,除第一、二题外,其他题目完成时间短,正确率高。
Comments 2 条评论
请教一下你这个代码块的样式是怎么弄的
@416365641 下了个markdown转换html为的插件,直接用markdown写的文章,代码块就直接markdown语法,最后会自动转成html页面,不需要我操作
(=・ω・=)