leetcode刷题记录二

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 0320 1 → 1 1 \rightarrow 1 11(自循环)。

先思考这样一个问题:Meeting with Aliens UVA - 10570, n n n个数的排列,一次操作选任意两个数进行交换,问使得排列变为升序或者降序的最少操作次数?

排列按照上述方式构图后如果只由一个循环组成,比如[2, 3, 1, 0]这样一个排列,那么所需交换次数最少为环中节点个数减一,既 o p m i n = n − 1 op_{min} = n - 1 opmin=n1。如果由多个环构成,假设环的个数为 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(cnti1),整理哈,既 o p m i n = n − t op_{min} = n - t opmin=nt

将问题再扩展一下: 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],会发现如果你不向前遍历的话,没法知道是哪个字符ba发生了交换,所以,让我们愉快的爆搜吧。这儿加了三个剪枝:①向后遍历看两个字符串是否已经相等②如果当前所需交换次数大于等于已经得到的最优交换次数(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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值