92. 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
题解
两个思路:①不断调换指针②不断交换值。因为 m m m可能等于1,所以要添加一个头节点(好写)
用循环比用while方便多了,还不容易出bug
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* root = new ListNode(0); // 加一个头节点很方便
root->next = head;
ListNode* copy_head = root;
for (int i = 1; i < m; ++i) root = root->next;
/*
与for循环的功能一样,找出翻转位置的前一个节点
注意:index++;不要放错位置!!!
int index = 0;
while(root->next != NULL) {
if (index + 1 >= m) break;
root = root->next;
index++;
}
*/
head = root->next;
for (int i = m; i < n; ++i) {
ListNode* temp = root->next;
root->next = head->next;
head->next = head->next->next;
root->next->next = temp;
}
return copy_head->next;
/*
同上面的for循环一样的功能
ListNode* start = root;
head = root->next;
int cnt = 0;
while(head->next != NULL) {
++cnt;
if (cnt == n - m + 1) break;
ListNode* temp = start->next;
start->next = head->next;
head->next = head->next->next;
start->next->next = temp;
}
return copy_head->next;
*/
}
};
765. 情侣牵手
N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。
这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。
题解
这道题蕴含了一个经常拿来出题的考点:对于任意一个排列,按照下标和值建边构成的图由一个个不相交的循环组成。举例来说:[3, 1, 0, 2]
,按照上述方式构图有两个循环:
0
→
3
→
2
→
0
0 \rightarrow 3 \rightarrow 2 \rightarrow 0
0→3→2→0 和
1
→
1
1 \rightarrow 1
1→1(自循环)。
先思考这样一个问题:Meeting with Aliens UVA - 10570, n n n个数的排列,一次操作选任意两个数进行交换,问使得排列变为升序或者降序的最少操作次数?
排列按照上述方式构图后如果只由一个循环组成,比如[2, 3, 1, 0]
这样一个排列,那么所需交换次数最少为环中节点个数减一,既
o
p
m
i
n
=
n
−
1
op_{min} = n - 1
opmin=n−1。如果由多个环构成,假设环的个数为
t
t
t,每个环的节点个数为
c
n
t
i
cnt_i
cnti,那么所需交换次数最少为
∑
i
=
0
t
(
c
n
t
i
−
1
)
\sum_{i = 0}^t (cnt_i - 1)
∑i=0t(cnti−1),整理哈,既
o
p
m
i
n
=
n
−
t
op_{min} = n - t
opmin=n−t。
将问题再扩展一下:
n
n
n个数,给出两个排列,分别记为排列A和排列B,一次操作:在A中任意选两个数进行交换,求最少交换次数使得排列A等于排列B?举例:A: [3, 2, 4, 1]
、B: [1, 3, 2, 4]
,需要三次操作,A才能变成B。
想一下会发现,B和基础问题的下标(从1开始)是等价的,都是A的一个置换,所以以 A [ i ] → B [ i ] A[i] \rightarrow B[i] A[i]→B[i]连边来进行构图,图的性质不会有任何变化。
有兴趣的童鞋可以尝试用数学归纳法证明单个循环的情况(黑体字部分),注意:两个环之间不需要交换操作,既只在环内进行交换操作。因为交换到另一个环后,最终又要交换回来,毕竟它在升序或者降序中的位置就在刚开始的环上。
对于这道题,恰恰就是上面的扩展问题。先把情侣的一人固定在当前位置上,然后交换另一个人来凑成情侣。复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( 1 ) O(1) O(1)。如果采取统计环的个数的方法,可以用并查集。
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int n = row.size();
int ans = 0;
for (int i = 0; i < n; i += 2) {
if (judeg(row[i], row[i + 1])) continue;
for (int j = i + 2; j < n; j += 2) {
if (judeg(row[j], row[j + 1])) continue;
if (judeg(row[i], row[j])) {
swap(row[i + 1], row[j]);
ans++;
break;
}
else if (judeg(row[i], row[j + 1])) {
swap(row[i + 1], row[j + 1]);
ans++;
break;
}
}
}
return ans;
}
bool judeg(int a, int b) {
if (a > b) swap(a, b);
if (b - a > 1) return false;
if (!(a & 1) && (b & 1)) return true;
return false;
}
};
第二个循环的作用是找它的配偶所在位置并交换,所以用一个数组记录一下,每个人的位置。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n).
这叫有失有得,另外,学习优化代码可重要了
class Solution {
public:
int minSwapsCouples(vector<int>& row)
{
int n = row.size(), ans = 0;
vector<int> pos(n, -1);
for (int i = 0; i < n; ++i) pos[row[i]] = i;
for (int i = 0; i < n; i += 2) {
int wife = row[i] ^ 1;
if (wife != row[i + 1]) {
int id = pos[wife];
swap(row[i + 1], row[id]);
pos[row[id]] = id;
ans++;
}
}
return ans;
}
};
854. 相似度为 K 的字符串
如果可以通过将 A 中的两个小写字母精确地交换位置 K 次得到与 B 相等的字符串,我们称字符串 A 和 B 的相似度为 K(K 为非负整数)。
给定两个字母异位词 A 和 B ,返回 A 和 B 的相似度 K 的最小值。
题解
和上一道题相比,相当于排列中出现了重复的数字(这时候不知道还能不能叫做排列),依然能采取上题的构图方式(只是会出现重边),然后统计环的个数或者统计环内节点的个数。
自环不用考虑,构图的过程中忽略就行;重边,貌似可以用链式前向星,然后标记一下边就可以了。不过我没尝试,直接用的DFS。
考虑样例[abbb]
和 [bbab]
,会发现如果你不向前遍历的话,没法知道是哪个字符b
和a
发生了交换,所以,让我们愉快的爆搜吧。这儿加了三个剪枝:①向后遍历看两个字符串是否已经相等②如果当前所需交换次数大于等于已经得到的最优交换次数(ans),直接返回③如果交换当前字符能够成功匹配两个位置(比如[ab]
和[ba]
,交换字符a
,第一个字符串就变为[ba]
,显然,两个位置都匹配成功),说明当前的递归方向是最优的。复杂度
O
(
2
n
)
O(2^n)
O(2n)
leetcode为啥一直不给数据规模呢?如果是其它oj,小数据不是状压就是爆搜+剪枝,而leetcode,想上爆搜,但砖头一想,会不会有线性算法或者多项式算法。orz
class Solution {
public:
int ans = 0x3f3f3f3f;
string b;
void DFS(int pos, int cnt, string copy_A) {
// 剪枝
if (cnt >= ans) return;
// 剪枝
for (int i = pos; i < copy_A.size(); ++i) {
if (copy_A[i] == b[i]) pos++;
else break;
}
if (pos >= copy_A.size()) {
ans = min(ans, cnt);
return;
}
if (copy_A[pos] == b[pos]) DFS(pos + 1, cnt, copy_A);
else {
// 剪枝
for (int i = pos + 1; i < b.size(); ++i) if (b[i] == copy_A[pos] && copy_A[i] == b[pos]) {
swap(copy_A[pos], copy_A[i]);
DFS(pos + 1, cnt + 1, copy_A);
return;
}
for (int i = pos + 1; i < copy_A.size(); ++i) if (copy_A[i] == b[pos]) {
swap(copy_A[pos], copy_A[i]);
DFS(pos + 1, cnt + 1, copy_A);
swap(copy_A[pos], copy_A[i]);
}
}
}
int kSimilarity(string A, string B) {
b = B;
DFS(0, 0, A);
return ans;
}
};
面试题51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
题解一
常见题,归并排序,每次更新l的时候,就统计右边有哪些比当前数小就行了
主要是和上道题进行比较,此题等价版本是给一个排列(可以出现重复的数),每次只能交换相邻的两个数,问最少交换多少次使得排列升序?
class Solution {
public:
int ans = 0;
vector<int> temp;
void merge(vector<int>& a, int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
merge(a, l, mid);
merge(a, mid + 1, r);
temp.clear();
int l_l = l, r_l = mid + 1;
while(l_l <= mid && r_l <= r) {
if (a[l_l] <= a[r_l]) temp.push_back(a[l_l]), l_l++, ans += r_l - (mid + 1);
else temp.push_back(a[r_l]), r_l++;
}
while(l_l <= mid) temp.push_back(a[l_l]), l_l++, ans += r_l - (mid + 1);
while(r_l <= r) temp.push_back(a[r_l]), r_l++;
for (int num: temp) a[l++] = num;
}
int reversePairs(vector<int>& nums) {
merge(nums, 0, nums.size() - 1);
return ans;
}
};
题解二
树状数组,倒着来,常规操作
class Solution {
public:
int m;
vector<int> sum;
const int lowbit(int x) {
return x & (-x);
}
void add(int pos) {
for (int i = pos; i <= m; i += lowbit(i)) sum[i]++;
}
int get(int pos) {
int ans = 0;
for (int i = pos; i >= 1; i -= lowbit(i)) ans += sum[i];
return ans;
}
int reversePairs(vector<int>& nums) {
vector<int> copy_nums(nums);
sort(copy_nums.begin(), copy_nums.end());
m = unique(copy_nums.begin(), copy_nums.end()) - copy_nums.begin();
sum.resize(m + 2, 0);
int ans = 0;
for (int i = nums.size() - 1; i >= 0; --i) {
int pos = lower_bound(copy_nums.begin(), copy_nums.begin() + m, nums[i]) - copy_nums.begin() + 1;
ans += get(pos - 1);
add(pos);
}
return ans;
}
};