图的一些算法题2

leetcode 765

N couples sit in 2N seats arranged in a row and want to hold hands. We want to know the minimum number of swaps so that every couple is sitting side by side. A swap consists of choosing any two people, then they stand up and switch seats.

The people and seats are represented by an integer from 0 to 2N-1, the couples are numbered in order, the first couple being (0, 1), the second couple being (2, 3), and so on with the last couple being (2N-2, 2N-1).

The couples' initial seating is given by row[i] being the value of the person who is initially sitting in the i-th seat.

思路:

题意大致为:给你N对数,通过一系列swap之后,使得在每对数中,一个数为偶数,另一个数比它大1。求需要的swap的次数。

题目归类为graph,然而直接的解题方法比图的方法更容易想到:用队列的思想,如果队头的一对数满足要求,直接pop,否则找到一个合适的数对,与之swap(可能swap之后两对数都满足要求,也可能只有对头满足要求),计数加一,再pop。代码如下:

class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int count = 0;
        while(!row.empty()) {
            if(row[0]%2==0 && row[1]==row[0]+1 || row[1]%2==0 && row[0]==row[1]+1) {
                pop(row, 0);
            } else {
                if(row[0]%2 == 0) {
                    int i;
                    for(i = 0; i < row.size(); i++)
                        if(row[i] == row[0]+1)
                            break;
                    swap(1, i, row);
                    pop(row,0);
                    count++;
                } else {
                    int i;
                    for(i = 0; i < row.size(); i++)
                        if(row[i] == row[0]-1)
                            break;
                    swap(1, i, row);
                    pop(row,0);
                    count++;                   
                }
            }
        }
        return count;
    }
    void pop(vector<int>& row, int p) {
        auto i = row.begin()+p;
        row.erase(i);
        row.erase(i);
    }
    void swap(int a, int b, vector<int>& row) {
        int t = row[a];
        row[a] = row[b];
        row[b] = t;
    }
};

那么如何用图的思路来做呢?首先将原始的每个数对看做一个结点,如果两个数对通过交换能够使得其中的一个或者两个数对满足条件,结点之间连一条边,最终形成一个无向图。swap的次数即为边的个数减去环的个数。(环中的边数比处理环中的数对需要的次数多一)。

leetcode 839

Two strings X and Y are similar if we can swap two letters (in different positions) of X, so that it equals Y.

For example, "tars" and "rats" are similar (swapping at positions 0 and 2), and "rats" and "arts" are similar, but "star" is not similar to "tars""rats", or "arts".

Together, these form two connected groups by similarity: {"tars", "rats", "arts"} and {"star"}.  Notice that "tars" and "arts" are in the same group even though they are not similar.  Formally, each group is such that a word is in the group if and only if it is similar to at least one other word in the group.

思路:每个字符串作为一个结点,如果两个字符串是“相似”的,那么两个结点相连。题目也就是让我们求其中连通图的数量。常规的方法就是构建邻接表,DFS出所有连通图(效率比较低,beat 15),代码如下:

class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        map<int, vector<int>> adj;
        int size = A.size();
        set<int> s;
        for(int i = 0; i < size-1; i++) {
            s.insert(i);
            for(int j = i+1; j < size; j++) {
                if(connect(A[i], A[j])) {
                    adj[i].push_back(j);
                    adj[j].push_back(i);
                }
            }
        }
        s.insert(size-1);
        int times = 0;
        while(!s.empty()) {
            auto t = s.begin();
            DFS(*t, s, adj);
            times++;
        }
        return times;
    }
    bool connect(string& a, string& b) {
        int count = 0;
        for(int i = 0; i < a.length(); i++)
            if(a[i] != b[i])
                count++;
        return  count==2||count==0;
    }
    void DFS(int node, set<int>& s, map<int, vector<int>>& adj) {
        if(s.find(node) == s.end())
            return;
        s.erase(node);
        for(auto&i:adj[node]) {
            DFS(i, s, adj);
        }
    }
};

另外,这一题可以使用并查集(disjoint-set或union-find)来解决问题。

并查集:

  一种数据结构,将一个集合分为不相交的子集,在添加新的子集,合并现有子集,判断元素所在集合时时间复杂度接近常数级。常用于求连通子图和最小生成树的Kruskal算法。

操作:

  makeSet:  初始化,给每个元素分配一个特定的id,以及一个指向自己的指针,表示每个元素都在一个大小为1的集合当中。

  find:       查找某个元素的根元素。当两个元素拥有同样的根元素时,说明他们在同一个集合当中。为了使得时间复杂度接近常数级,在查找的过程中,可以更新指针指向根元素(代码中使用递归方法),有人称其为路径压缩。

  Union:      满足条件则合并两个子集。如果没有特殊要求,将一个集合的根指向另一个集合的根即可。也可根据秩或者集合的大小来指定。

所以对于这一题,我们建立一个并查集,子集的数量就是连通子图的数量,也就是需要的group的数量。效率得到提高(beat 75)

代码如下:

class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        int size = A.size();
        vector<int> next(size);
        makeSet(next);
        int s = size;
        for(int i = 0; i < s-1; i++) {
            for(int j = i+1; j < s; j++) {
                if(connect(A[i], A[j])) {
                    unionSet(i, j, next, size);
                }
            }
        }
        return size;
    }
    bool connect(string& a, string& b) {
        int count = 0;
        for(int i = 0; i < a.length(); i++)
            if(a[i] != b[i])
                count++;
        return  count==2||count==0;
    }
    void makeSet(vector<int>& next) {
        int j = 0;
        for(auto& i:next)
            i = j++;
    }
    void unionSet(int i, int j, vector<int>& next, int& size) {
        int root_i = find(i, next), root_j = find(j, next);
        if(root_i != root_j) {
            next[root_i] = root_j;
            size--;
        }
    }
    int find(int i, vector<int>& next) {
        if(i != next[i])
            next[i] = find(next[i], next);
        return next[i];
    }
};

leetcode 854

Strings A and B are K-similar (for some non-negative integer K) if we can swap the positions of two letters in A exactly K times so that the resulting string equals B.

Given two anagrams A and B, return the smallest K for which A and B are K-similar.

思路:尝试使用贪心算法。策略为:替换只在不符合的字母之间进行,如果一次替换使得两个不符合的数同时符合,这个替换优先执行。然而,行不通,只通过了一半的测例。如"aabbccddee" "cdacbeebad"就无法通过。显然,某次替换会对之后的替换产生潜在影响。那……就用DFS把所有情况找出来,找出最小K的方案吧(暴力求解)。代码如下:

class Solution {
private:
    int size;
    int new_size;
    int result;
public:
    int kSimilarity(string A, string B) {
        this->size = A.length();
        string a, b;
        for(int i = 0; i < this->size; i++) {
            if(A[i] != B[i]) {
                a.push_back(A[i]);
                b.push_back(B[i]);
            }
        }
        this->new_size = a.length();
        if(this->new_size == 0)
            return 0;
        this->result = INT_MAX;
        DFS(a, b, 0, 0);
        return this->result;
    }
    void DFS(string a, string b, int p, int k) {
        if(p == this->new_size-1) {
            this->result = min(this->result, k);
            return;
        }
        if(a[p] == b[p]) {
            DFS(a, b, p+1, k);
            return;
        }
        for(int j = p+1; j < this->new_size; j++) {
            if(a[p] == b[j]) {
                swap(b[p], b[j]);
                DFS(a, b, p+1, k+1);
                swap(b[p], b[j]);
            }
        }
    }
    void swap(char& a, char& b) {
        char t = a;
        a = b;
        b = t;
    }
};

另外,还可以通过存储已经计算过的经过某些交换后的串的K值来避免重复计算,进而提高效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值