1. 最小字典序旋转链表
题目
给出一个单向无环链表,其中的节点定义如下:
struct ListNode{
int val;
ListNode* next;
}
定义一个链表的旋转链表为:将链表头部的若干连续节点移到尾部。如链表{5,3,2,1,4}的所有旋转链表为{5,3,2,1,4}(自身),{3,2,1,4,5},{2,1,4,5,3},{1,4,5,3,2},{4,5,3,2,1}。
定义链表字典序大小关系为:对于链表a和b,如果存在k,使得对任意i<k,有a_i<b_i,且a_k=b_k,则称a的字典序小于b。其中a_i表示a从头节点开始的第i个后继节点(头节点自身为a_1)。
(其实这里的字典序就和字符串一样,是字面意思。)
完成如下函数,使其返回参数链表的所有旋转链表中字典序最小的一个:
class Solution{
public:
ListNode* Solve(ListNode* S){
//Write your code here.
}
}
如对于链表{5,3,2,1,4},应该返回 {1,4,5,3,2};对于{2,2,5,2,2},应该返回{2,2,2,2,5}。
输入:链表头节点ListNode* s。
输出:s的最小字典序旋转链表头节点。
等价题目:POJ 1509
解法
A.暴力
初始设ans为S,然后对所有后继节点循环。对每个后继节点temp,依次比较ans和temp的所有后继节点,直到val不同或者比较完整个链表。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)。
我是这么做的,但只过了92.84%的case,然后告知我有段错误。我百思不得其解,唯一能想到的解释是可能没处理好nullptr的情况,但那也不该过这么多case啊……听说有人写这个过了。
B.最小表示法
该算法本身是处理字符串的一个算法,和本题的本质是一样的,下面详述。
任务:对于长度为
n
n
n的字符串s,将其复制一份并加在尾部,形成长度为
2
n
2n
2n的字符串str。现要在其所有长度为
n
n
n的子串中找到字典序最小者。
最小表示法能以
O
(
n
)
O(n)
O(n)的时间复杂度完成之。
初始,设i=0, j=1, k=0。这里的i和j是即将做比较的可能答案的开始位置,k是从它俩开始的相同的字符串的长度。做如下循环:
如果str[i+k]==str[j+k],则k++;
如果str[i+k]<str[j+k],表明str[i:i+k-1]==str[j:j+k-1],所以j, j+1, ..., j+k都不可能是答案的位置,接下来应该比较的位置是i和j=j+k+1;
如果str[i+k]>str[j+k],与上一种情况相似,所以接下来应该比较的是i+k+1和j。
代码如下:
int GetMin(string s){
int l = s.strlen();
int i = 0, j = 1, k = 0;
int diff;
while(i < n && j < n && k < n){
t = s[(i+k)%n] - s[(j+k)%n];
if(t==0) k++;
else{
if(t>0) i = i+k+1;
else j = j+k+1;
if(i==j) j++;//or i++
k=0;
}
}
return i;
}
时间复杂度: O ( n ) O(n) O(n)。
2. 发放广告
题目
有n个接收广告的用户,每个人每隔一段时间要接收一条广告,第i个用户的接收间隔为t_i。现在要发送k条广告,请按时间顺序输出接收广告的用户编号。如果某时刻要同时给多个用户发送广告,则按编号从小到大输出。
输入:n+1行。第一行输入n和k,后面n行中的第i行为用户i的接收间隔。
输出:k行。第k行为第k个接收广告的用户编号。
解法
A.暴力
设时间变量tik,对其循环:对于所有t[i],计算tik%t[i],如果为0则输出,并增加已投放广告数量,如果数量达到k则跳出;遍历完t[n]后,tik++。
时间复杂度:一个很坏的情况下为
O
(
n
k
max
(
t
[
i
]
)
)
O(nk\max(t[i]))
O(nkmax(t[i]))(只有一个用户,其时间间隔极大)。我尚未证明这是最坏情况,但已经非常糟糕。我如此做,只过了好像70%的case。
B.堆排序
为数组t[n]建立一个最小堆,堆中每个节点表示一个用户,同时具有下次接收广告时间tNext和用户编号no属性。比较节点时,优先比较tNext,相同时比较no。
class Node{
public:
int tNext;
int no;
Node* left, right;
Node(int t_, int no_){
tNext = t_;
no = no_;
left = nullptr;
right = nullptr;
}
}
bool operator<(Node* a, Node* b){
if(a.tNext != b.tNext) return a.tNext < b.tNext;
return a.no < b.no;
}
每次取出堆顶的最小节点,输出其编号,删除该节点,更新堆,然后添加一个Node(tNext+t[no], no)节点,更新堆。
时间复杂度:建堆和更新堆都需要
O
(
n
log
n
)
O(n\log n)
O(nlogn),共更新
2
k
2k
2k次,总的时间复杂度为
O
(
n
k
log
n
)
O(nk\log n)
O(nklogn)。
3. 游戏俱乐部
题目
俱乐部里有很多游戏。完成每个游戏,都需要1单位的时间,且都可以得到一定的分数。但是每个游戏都必须在某个时间点之前完成,否则得不到其分数。问如何得到最高分数。
输入:游戏数量n,每个游戏的截止时间t_i,每个游戏的分数s_i。
输出:最高可得到的分数。
解法:贪心
对时间点i做循环:在截止时间为i的所有游戏里,完成分数最高的那个。如果没有游戏截止时间为i,则继续在截止时间为i+1的游戏里找,以此类推。找到后令i++,直到i==max(t[n])或者所有游戏都被玩过为止。
时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)。先把所有游戏按截止时间和分数排序,时间为
O
(
n
log
n
)
O(n\log n)
O(nlogn);然后至多找到n个游戏。(?)
4. 递归相等字符串
题目
有两个长度相等的字符串a和b。我们如下定义a和b的相等:如果两者的长度为奇数,则要求两者中相同位置的字符都相同(即普通意义上的相等);如果两者的长度为偶数,则将它们都从正中间切成两个字符串,分别是a1, a2和b1, b2,要求a1与b1、a2与b2相等,或者a1与b2、a2与b1相等(这里的两个相等是递归定义的)。
输入:数据组数n,和n对长度相等的字符串。
输出:n行,如果一对字符串相等则输出YES,否则输出NO。
解法:递归
理论上,按照题中要求递归地实现函数bool Equal(string a, string b)即可。
但是这样会超时,因为生成新字符串消耗了很多时间。改为bool Equal(char* a, char* b, int length)后即可通过。
时间复杂度:
O
(
n
)
O(n)
O(n)。因为只有递归到最后一层(字符串长度为奇数)时才逐个比较字符,其他层直接利用更深层的返回值。
5. 打地鼠
题目
有一片m*n大小的场地,其中每个格子都会冒出地鼠。给出矩阵mp[m][n],第i行第j列的格子会每隔mp[i][j]时间冒出一只地鼠(均从0时刻开始)。
再给出时间t,你需要在这段时间里在场地上移动,每次必须在水平或垂直方向移动一格,且不能移到上一时刻所在的格子。如果在某一时刻,你所在的格子有地鼠冒出,则可以打掉地鼠获得1分。在t时刻,如果你不在第m行第n列,则积分清零。
输入:矩阵mp和时间t。
输出:时间t过后,最高可得到的分数。
解法:动归
没做出来……待续。
本文解析了五道经典算法题,包括最小字典序旋转链表、发放广告、游戏俱乐部、递归相等字符串及打地鼠。针对每道题目,提供了多种解法思路,如暴力解法、最小表示法、贪心算法、递归算法和动态规划等。

3029

被折叠的 条评论
为什么被折叠?



