LeetCode_Dec_1st_Week

December 6th : 1816. 截断句子
December 7th : 1034. 边界着色
December 8th : 689. 三个无重叠子数组的最大和
December 9th : 794. 有效的井字游戏
December 10th : 748. 最短补全词
December 11st : 911. 在线选举
December 12nd : 709. 转换成小写字母


December 6th : 1816. 截断句子

今天我就24周岁啦!承蒙厚爱~ ,得到了人生中的第一个乐高模型,许愿今后能开起不差的B级车吧~💪💪💪!感谢亲爱的!刷个题助助兴 ~

读过题,单词间是固定单个空格的,那么就记录还剩余多少单词没有加上,同时维护窗口,窗口的左右字符都是空格,则说明是一个单词。这样遍历整个输入字符串,然后截取出前k个单词,并拼接成字符串返回即可。

class Solution {
public:
    string truncateSentence(string s, int k) {
        string ret;
        for(int i = 0; i < s.size(); ++i) {
            if(s[i] == ' ') continue;
            if(k == 0) break; //单词截取完了
            int l = i, r = i; //窗口左右边界
            while(r < s.size() && s[r] != ' ') r++; //没遇到空格,则扩大窗口
            for(int j = i; j < r; ++j) ret += s[j]; //把窗口中单词加到结果字符串上
            k--; i = r;  //更新剩余单词数,同时更新i的值,以作为新的窗口的初始化值
            if(k != 0) ret += " "; //如果不是末尾单词,要加上空格作为分隔符
        }
        return ret;
    }
};

December 7th : 1034. 边界着色

将连通集的边界(网格边界,和不同颜色的边界)染成指定的颜色。就相当于小岛的题,使用 d f s dfs dfs或者 b f s bfs bfs均可。这里使用 d f s dfs dfs,其中如何求得边界是关键,当 d f s dfs dfs到网格边界或者发现相邻格子和初始格子的颜色不一致时,则可判定当前所遍历到的格子是边界格子。再将边界的格子着上指定的颜色即可。

class Solution {
public:
    vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
        if(row < 0 || row >= grid.size() || col < 0 || col >= grid[0].size()) return grid;
        visited = vector<vector<int>>(grid.size(), vector<int>(grid[0].size(), 0)); //初始化访问记录
        dfs(grid, visited, row, col, color); //进行dfs
        for(int i = 0; i < grid.size(); ++i) //进行边界着色
            for(int j = 0; j < grid[0].size(); ++j)
                if(visited[i][j] == 2) grid[i][j] = color;
        return grid;
    }

private:
    vector<int> direction = {-1, 0, 1, 0, -1}; //方向向量
    vector<vector<int>> visited; //记录是否访问过,进行dfs剪枝,同时能记录边界
    void dfs(vector<vector<int>>& grid, vector<vector<int>>& visited, int row, int col, int color) {
        if(row < 0 || row >= grid.size() || 
           col < 0 || col >= grid[0].size() || visited[row][col]) return; //如果坐标不合法或已经访问过则返回
        visited[row][col] = 1; //标记已经遍历过该坐标
        for(int i = 0; i < 4; i++) { //遍历当前坐标上下左右的四个坐标
            int row_new = row + direction[i], col_new = col + direction[i+1];
            if(row_new >= 0 && row_new < grid.size() && 
                col_new >= 0 && col_new < grid[0].size() &&
                grid[row][col] == grid[row_new][col_new]) //如果符合,则说明没有到达连通集边界
                dfs(grid, visited, row_new, col_new, color);
            else visited[row][col] = 2; //如果不符合,则说明到达连通集边界,将当前坐标标记为2
        }
    }
};

December 8th : 689. 三个无重叠子数组的最大和

使用三个大小为k的滑动窗口。设 s u m 1 sum_1 sum1为第一个滑动窗口的元素和,该滑动窗口从 [ 0 , k − 1 ] [0,k-1] [0,k1]开始; s u m 2 sum_2 sum2为第二个滑动窗口的元素和,该滑动窗口从 [ k , 2 k − 1 ] [k,2k-1] [k,2k1]开始; s u m 3 sum_3 sum3为第三个滑动窗口的元素和,该滑动窗口从 [ 2 k , 3 k − 1 ] [2k,3k-1] [2k,3k1]开始。

同时向右滑动三个窗口,维护 m a x S u m 12 maxSum_{12} maxSum12(表示第一二窗口的最大和)及对应位置。每次滑动,计算当前的 m a x S u m 12 maxSum_{12} maxSum12 s u m 3 sum_3 sum3之和。统计过程中 m a x S u m 12 + s u m 3 maxSum_{12}+sum_3 maxSum12+sum3的最大值和各窗口的对应位置。

由于我们是从左向右遍历,当且仅当三个窗口元素和大于的最大值和时才修改元素和,所以更够保证是最小字典序的。

class Solution {
public:
    vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {
        vector<int> ans;
        int sum1 = 0, maxSum1 = 0, maxSum1Idx = 0;
        int sum2 = 0, maxSum12 = 0, maxSum12Idx1 = 0, maxSum12Idx2 = 0;
        int sum3 = 0, maxTotal = 0;
        for (int i = k * 2; i < nums.size(); ++i) { //将第三个窗口的初始左边界作为i的初始值
            //如果没到第三个窗口的初始右边界,则进行窗口中的值的累加
            sum1 += nums[i - k * 2];
            sum2 += nums[i - k];
            sum3 += nums[i];
            //当i已经超出第三个窗口的初始右边界时,所有窗口中的值都会发生变化
            if (i >= k * 3 - 1) {
                if (sum1 > maxSum1) { //第一个窗口找到了较大和
                    maxSum1 = sum1; //更新第一个窗口的最大值
                    maxSum1Idx = i - k * 3 + 1; //更新记录的下标
                }
                //第二个窗口和和第一个窗口最大和相加大于先前计算的第一二窗口的最大和
                if (maxSum1 + sum2 > maxSum12) { 
                    maxSum12 = maxSum1 + sum2; //更新第一二窗口的最大和值
                    maxSum12Idx1 = maxSum1Idx; //更新此时的第一个窗口的左边界
                    maxSum12Idx2 = i - k * 2 + 1; //更新此时的第二个窗口的左边界
                }
                //第一二窗口的最大和和第三个窗口和相加大于之前三个窗口的最大和
                if (maxSum12 + sum3 > maxTotal) { 
                    maxTotal = maxSum12 + sum3; //更新三个窗口最大和
                    ans = {maxSum12Idx1, maxSum12Idx2, i - k + 1}; //更新左边界坐标
                }
                //去除旧左边界的值,为接下来的for轮窗口右移做准备
                sum1 -= nums[i - k * 3 + 1]; 
                sum2 -= nums[i - k * 2 + 1];
                sum3 -= nums[i - k + 1];
            }
        }
        return ans;
    }
};

December 9th : 794. 有效的井字游戏

搞清楚怎么才是有效的是解题的关键:

  • 首先是X起手,所以X的个数要比O多一个或者是一样多;
  • ① 其次,如果X比O多,则不存在O结束游戏的情况(一行/列,或主/副对角线全是O),这样X的个数才可能比O多;
  • ② 同理,如果X和O一样多,则说明不存在X结束游戏的情况(一行/列,或主/副对角线全是X)。

理清了只有①②这两种情况才是有效,再将思路编写出代码即可。

class Solution {
public:
    bool validTicTacToe(vector<string>& board) {
        int cnt = 0, w[128]{}; //cnt记录XO的差值,O为负
        //得到差值,只有两种情况,一是X比O多一个此时cnt==1,二是X和O一样多此时cnt==2
        for(auto&& s: board) for(char c: s) cnt += c == 'O' ? -1 : c == 'X'; 
        //扫描各行各列,查找是否有一行或一列都是X或O的情况,
        //如果不全是,则不会映射到X或O,即w['X']或w['O']为0
        for(int i = 0; i < 3; ++i){
            ++w[board[i][0] | board[i][1] | board[i][2]];   
            ++w[board[0][i] | board[1][i] | board[2][i]];    
        }
        //扫描主对角线和副对角线
        ++w[board[0][0] | board[1][1] | board[2][2]];
        ++w[board[0][2] | board[1][1] | board[2][0]]; 
        //只有两种情况下,才是可能的:
        //一是X比O多(cnt==1),且O不存在于一行/列,或主/副对角线(w['O']==0)
        //二是X和O一样多(cnt==0),且X不存在于一行/列,或主/副对角线(w['X']==0)
        return cnt == 1 && !w['O'] || cnt == 0 && !w['X'];
    }
};

December 10th : 748. 最短补全词

把题意弄清楚,然后就是简单的字符串遍历和频次统计的问题了。

class Solution {
public:
    string shortestCompletingWord(string licensePlate, vector<string>& words) {
        int destIdx = -1; bool flg = true;
        int plate[26] = {0}, word[26] = {0};
        for(int i = 0; i < licensePlate.size(); ++i) 
            if(isLetter(licensePlate[i])) plate[getIdx(licensePlate[i])]++;
        for(int i = 0; i < words.size(); ++i) {
            memset(word, 0, 26*sizeof(int));
            for(int j = 0; j < words[i].size(); ++j) 
                if(isLetter(words[i][j])) word[getIdx(words[i][j])]++;
            for(int k = 0; k < 26; ++k) 
                if(plate[k] > word[k]) {flg = false; break;}
            if(flg) {
                if(destIdx == -1) destIdx = i;
                else destIdx = words[destIdx].size() > words[i].size() ? i : destIdx; 
            }
            flg = true;
        }
        return words[destIdx];
    }
private:
    bool isLetter(char ch) {
        if(ch <= 'z' && ch >= 'a' || ch <= 'Z' && ch >= 'A') return true;
        else return false;
    }
    int getIdx(char ch) {
        if(ch <= 'z' && ch >= 'a') return ch - 'a';
        else return ch - 'A';
    }
};

December 11st : 911. 在线选举

我在写的时候想复杂了,用的pair数组这个数据结构,其实单独开一个记录票数最高的数组,将初始化的时间数组复用即可。对于查询,根据要查询时间t,找出投票时间中早于t时间的最晚的投票时间(二分查找不大于查询值的右边界的情形,具体见:有序数组二分查找模板:③ 最后的小于等于目标值target的数,即右边界),将该时间的最高得票者返回即可。

//version1
class TopVotedCandidate {
public:
    vector<int> tops;
    vector<int> times;

    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        unordered_map<int, int> voteCounts;
        voteCounts[-1] = -1;
        int top = -1;
        for (auto & p : persons) {
            voteCounts[p]++;
            if (voteCounts[p] >= voteCounts[top]) {
                top = p;
            }
            tops.emplace_back(top);
        }
        this->times = times;
    }
    
    int q(int t) {
    	//寻找小于等于t时间的右边界的投票时间
        int pos = upper_bound(times.begin(), times.end(), t) - times.begin() - 1;
        return tops[pos];
    }
};

/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj->q(t);
 */
/**********************************************************************************************************/
//version2
class TopVotedCandidate {
public:
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        int winner = -1, votes = -1;
        //record.push_back(make_pair(times[0], persons[0]));
        for(int i = 0; i < persons.size(); ++i) {
            process[persons[i]]++;
            if(votes <= process[persons[i]]) {
                winner = persons[i]; 
                votes = process[winner];
            };
            record.push_back(make_pair(times[i], winner));
        }
        // for(int i = 0; i < record.size(); ++i) 
        //     cout << "Time:" << record[i].first << " " << "Winner:" << record[i].second << endl;
    }
    
    int q(int t) {
        cout << ++cnt << " q:";
        int l = 0, r = record.size()-1;
        while(l <= r) {
            int m = (l + r) >> 1;
            if(t == record[m].first) return record[m].second;
            else if(record[m].first > t) r = m - 1;
            else l = m + 1;
        } 
        return record[r].second;
    }

private:
    unordered_map<int, int> process;
    vector<pair<int, int>> record;
};
/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj->q(t);
 */

December 12nd : 709. 转换成小写字母

没什么好讲的,就是大写字母转为小写字母,其余的字符不变即可。这里看评论里有个技巧,提一下,但是因为有其余字符在,所以不适用,但是可以了解一下。

  • 大写变小写,小写变大写 : 字符 ^= 32;
  • 大写变小写,小写变小写 : 字符 |= 32;
  • 小写变大写,大写变大写 : 字符 &= -33;

所有小写字母的 ASCII 码的二进制第六位(从右向左)都是 1 ,而对应的大写字母第六位为 0 ,其他位都一样。 所以转换为小写,只需字符的 ASCII 码的第六位变为 1 ,即与 0b00100000 (32)做或运算,转换为大写即与0b11011111(-33)做与运算。

class Solution {
public:
    string toLowerCase(string s) {
        for(auto& ch : s) {
            if(ch <= 'Z' && ch >= 'A') 
                ch = 'a' + (ch - 'A');
        }
        // for(int i = 0; i < ret.size(); ++i) {
        //     if(ret[i] <= 'Z' && ret[i] >= 'A')
        //         ret[i] = 'a' + (ret[i] - 'A');
        // }
        //for(auto& ch : s) ch |= 32;
        return s;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值