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
过后,最高可得到的分数。
解法:动归
没做出来……待续。