leetcode解题思路分析(八十八)771 - 780 题

  1. 宝石与石头
    给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

集合的简单使用

class Solution {
    
    
public:
    int numJewelsInStones(string jewels, string stones) 
    {
    
    
        int cnt = 0;
        std::unordered_set<char> m_set;

        for (auto c : jewels)
        {
    
    
            m_set.insert(c);
        }

        for (auto c : stones)
        {
    
    
            if (m_set.find(c) != m_set.end())
            {
    
    
                cnt++;
            }
        }

        return cnt;
    }
};
  1. 滑动谜题
    在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示.一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换.最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。

通常此类问题可以使用「BFS」、「AStar 算法」、「康拓展开」进行求解。对于任意的 N * M 的数码问题,只要确保 M 为奇数,逆序对数量为偶数,必然有解(因为此时四联通的操作不会改变奇偶性)。

struct AStar {
    
    
    // 曼哈顿距离
    static constexpr array<array<int, 6>, 6> dist = {
    
    {
    
    
        {
    
    0, 1, 2, 1, 2, 3},
        {
    
    1, 0, 1, 2, 1, 2},
        {
    
    2, 1, 0, 3, 2, 1},
        {
    
    1, 2, 3, 0, 1, 2},
        {
    
    2, 1, 2, 1, 0, 1},
        {
    
    3, 2, 1, 2, 1, 0}
    }};

    // 计算启发函数
    static int getH(const string& status) {
    
    
        int ret = 0;
        for (int i = 0; i < 6; ++i) {
    
    
            if (status[i] != '0') {
    
    
                ret += dist[i][status[i] - '1'];
            }
        }
        return ret;
    };

    AStar(const string& status, int g): status_{
    
    status}, g_{
    
    g}, h_{
    
    getH(status)} {
    
    
        f_ = g_ + h_;
    }

    bool operator< (const AStar& that) const {
    
    
        return f_ > that.f_;
    }

    string status_;
    int f_, g_, h_;
};

class Solution {
    
    
private:
    vector<vector<int>> neighbors = {
    
    {
    
    1, 3}, {
    
    0, 2, 4}, {
    
    1, 5}, {
    
    0, 4}, {
    
    1, 3, 5}, {
    
    2, 4}};;

public:
    int slidingPuzzle(vector<vector<int>>& board) {
    
    
        // 枚举 status 通过一次交换操作得到的状态
        auto get = [&](string& status) -> vector<string> {
    
    
            vector<string> ret;
            int x = status.find('0');
            for (int y: neighbors[x]) {
    
    
                swap(status[x], status[y]);
                ret.push_back(status);
                swap(status[x], status[y]);
            }
            return ret;
        };

        string initial;
        for (int i = 0; i < 2; ++i) {
    
    
            for (int j = 0; j < 3; ++j) {
    
    
                initial += char(board[i][j] + '0');
            }
        }
        if (initial == "123450") {
    
    
            return 0;
        }

        priority_queue<AStar> q;
        q.emplace(initial, 0);
        unordered_set<string> seen = {
    
    initial};

        while (!q.empty()) {
    
    
            AStar node = q.top();
            q.pop();
            for (auto&& next_status: get(node.status_)) {
    
    
                if (!seen.count(next_status)) {
    
    
                    if (next_status == "123450") {
    
    
                        return node.g_ + 1;
                    }
                    q.emplace(next_status, node.g_ + 1);
                    seen.insert(move(next_status));
                }
            }
        }

        return -1;
    }
};

  1. 全局倒置与局部倒置
    给你一个长度为 n 的整数数组 nums ,表示由范围 [0, n - 1] 内所有整数组成的一个排列。
    全局倒置 的数目等于满足下述条件不同下标对 (i, j) 的数目:
    0 <= i < j < n
    nums[i] > nums[j]
    局部倒置 的数目等于满足下述条件的下标 i 的数目:
    0 <= i < n - 1
    nums[i] > nums[i + 1]
    当数组 nums 中 全局倒置 的数量等于 局部倒置 的数量时,返回 true ;否则,返回 false 。

局部倒置也是全局倒置,因此只要没有非局部倒置就是可以的
非局部倒置,就是满足 j>i+1 且 nums[i] > nums[j]
而实际就是倒序去比较,保存最小值,等价于 A[i] > min(A[i+2:*])


class Solution {
    
    
public:
    bool isIdealPermutation(vector<int>& nums) {
    
    
        int n = nums.size();
        int low = n;
        for (int i = n - 1; i >= 2; --i)
        {
    
    
            low = min(low, nums[i]);
            if (nums[i-2] > low)
            {
    
    
                return false;
            }
        }
        return true;
    }
};

  1. 在LR字符串中交换相邻字符
    在一个由 ‘L’ , ‘R’ 和 ‘X’ 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作。一次移动操作指用一个"LX"替换一个"XL",或者用一个"XR"替换一个"RX"。现给定起始字符串start和结束字符串end,请编写代码,当且仅当存在一系列移动操作使得start可以转换成end时, 返回True。

按照L和R不会穿透过去,即L和R的相对位置不会发生改变
那么就是如果消除字符串里的X,那么字符串要相等
对于每个L和R都有一个start和end的对应,存在两个约束
L不会移动到更小的序号
R不会移动到更大的序号


string stringReplace (const string& source,
                      const string& toReplace,
                      const string& replaceWith)
{
    
    
  size_t pos = 0;
  size_t cursor = 0;
  int repLen = toReplace.length();
  stringstream builder;

  do
  {
    
    
    pos = source.find(toReplace, cursor);

    if (string::npos != pos)
    {
    
    
        //copy up to the match, then append the replacement
        builder << source.substr(cursor, pos - cursor);
        builder << replaceWith;

        // skip past the match 
        cursor = pos + repLen;
    }
  } 
  while (string::npos != pos);

  //copy the remainder
  builder << source.substr(cursor);

  return (builder.str());
}

class Solution {
    
    
public:
    bool canTransform(string start, string end) {
    
    
        if (stringReplace(start, "X", "") != stringReplace(end, "X", ""))
        {
    
    
            return false;
        }

        // 依次遍历去判断L和R是否满足相对位置需要
        // end里对应的序号
        int d = 0;
        for (int i = 0; i < start.size(); ++i)
        {
    
    
            if (start[i] == 'L')
            {
    
    
                while (end[d] != 'L')
                {
    
    
                    ++d;
                }
                if (i < d)
                {
    
    
                    // cout << "L " << i << " " << d << endl;
                    return false;
                }
                ++d;
            }
        }

        d = 0;
        for (int i = 0; i < start.size(); ++i)
        {
    
    
            if (start[i] == 'R')
            {
    
    
                while (end[d] != 'R')
                {
    
    
                    ++d;
                }
                if (i > d)
                {
    
    
                    // cout << "R " << i << " " << d << endl;
                    return false;
                }
                ++d;
            }
        }

        return true;
    }
};

  1. 水位上升的泳池中游泳
    在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。
    现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。
    你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)?

二分+剪枝

class Solution {
    
    
public:
    int n;
    int dx[4]={
    
    0,0,-1,1};
    int dy[4]={
    
    1,-1,0,0};
    int visited[53][53];
    int flag=0;
    void dfs(int x,int y,vector<vector<int>>& grid,int m){
    
    
        if(flag==1) return;
        visited[x][y]=1;
        if(x==n-1 && y==n-1){
    
    
            flag=1;
            return ;
        }
        for(int i=0;i<4;i++){
    
    
            int now_x=x+dx[i];
            int now_y=y+dy[i];
            if(now_x>=0&&now_x<n&&now_y>=0&&now_y<n&&grid[now_x][now_y]<=m&&!visited[now_x][now_y]){
    
    
                dfs(now_x,now_y,grid,m);
            }
        }
    }
    bool okk(int m,vector<vector<int>>& grid){
    
    
        for (int i = 0; i < n; ++i) {
    
    
            for (int j = 0; j < n; ++j) {
    
    
                visited[i][j]=0;
                
            }
        }
        flag=0;
        if(grid[0][0]>m) return false;
        dfs(0,0,grid,m);
        return flag;

    }
    int swimInWater(vector<vector<int>>& grid) {
    
    
        if(grid.empty()) return 0;
        else{
    
    
         n=grid.size();
         int right=n*n-1;
         int left=0;
         while(left<right){
    
    
             int mid=(left+right)/2;
             if(okk(mid,grid)){
    
    
                 right=mid;
             }
             else{
    
    
                 left=mid+1;
             }
         }
         return left;


    }
    }
};
  1. 第K个语法符号
    在第一行我们写上一个 0。接下来的每一行,将前一行中的0替换为01,1替换为10。给定行数 N 和序数 K,返回第 N 行中第 K个字符。(K从1开始)

每一行的前半段就是上一行,后半段就是上一行的每个值反过来(0变1,1变0)
可以分为两种情况
如果K在前半段,所对应的值就是上一行的第K个值
如果K在后半段,可以先算出K相对于后半段的位置,然后找出上一行这个位置的值,把值反过来

class Solution {
    
    
public:
    int kthGrammar(int N, int K) {
    
    
        if(N == 1)return 0;

        // 计算当前行的长度:2的N-1次方
        int length = pow(2 ,(N - 1));

        // 如果K大于长度的一半,就是K所在位置是后半段
        if(K > length / 2){
    
    

            // 先得到上一行的值,位置是K相对于后半段的位置
            int val = kthGrammar(N - 1, K - length / 2);

            // 然后把值反过来
            return val == 0 ? 1 : 0;
        }
        // 否则前半部分
        else{
    
    

            // 值就是上一行K位置的值
            return kthGrammar(N - 1, K);
        }
    }
};


  1. 到达终点
    从点 (x, y) 可以转换到 (x, x+y) 或者 (x+y, y)。给定一个起点 (sx, sy) 和一个终点 (tx, ty),如果通过一系列的转换可以从起点到达终点,则返回 True ,否则返回 False。
class Solution {
    
    
public:
    bool reachingPoints(int sx, int sy, int tx, int ty) {
    
    
        // 排除边缘情况,必然不满足的
        if (sx > tx || sy > ty)
        {
    
    
            return false;
        }

        if (tx == ty)
        {
    
    
            // 相等的时候必须都相等才可以
            return sx == tx && sy == tx;
        }
        // 进行比较时候,总是考虑较大tx情况去处理,所以进行一次swap操作
        if (tx < ty)
        {
    
    
            swap(tx, ty);
            swap(sx, sy);
        }

        if (ty == sy)
        {
    
    
            // 不满足的情况下,必须正好能被整除才可以
            return (sx >=(tx%ty) && ((tx-sx) % sy == 0));
        }
        return reachingPoints(sx, sy, tx % ty, ty);
    }
};

猜你喜欢

转载自blog.csdn.net/u013354486/article/details/119768992