机试刷题
P1308 统计单词数
我的代码
首先,代码思路很正确:
- 将两行字符串转为小写
- 然后进行查找,查找是要判断是否为整个单词
- 若未查找到则返回-1,否则,一直往下寻找,知道查找完,输出频度
但是由于不太会用string类的函数,因此以下代码并不能AC
#include<iostream>
#include<string>
#include <vector>
using namespace std;
void XCHG (string &s){
for(int i = 0; i < s.length() ; i++){
if(s[i] >= 'A' && s[i] <= 'Z'){
s[i] = s[i] - 'A' + 'a';
}
}
}
int main(){
std::ios::sync_with_stdio(false);
string word,str;
vector<string>S;
getline(cin,word);
getline(cin,str);
XCHG(word);
XCHG(str);
int pos = h,num = 0;
while(h != str.npos){
if((h==0 || str[h-1] == ' ') && (((h+word.length()-1) == str.size()-1) || str[h+word.length()] == ' ' )) {
num++;
}
str.erase(h,word.length());
h = str.find(word);
}
if(num == 0) cout << -1 << endl;
else{
cout << num << " " << pos << endl;
}
return 0;
}
修改后代码
- 要转化小写字母,可以用tolower()函数,但需要用for循环遍历字符串
- 其次,不需要用erase()函数去除掉查找到的字符串,因为find()函数可以包含两个参数,参数1为string类字符串——要查找的字符串,参数2为int型——从该处开始查找。
- 还有一处,那就是,h = str.find(word,h+1); 这句话跳过了跳过了查找到的word了,因为word一定要全词匹配。
修改后AC的代码如下:
#include<iostream>
#include<string>
#include <vector>
using namespace std;
void XCHG (string &s){
for(int i = 0; i < s.length() ; i++){
if(s[i] >= 'A' && s[i] <= 'Z'){
s[i] = tolower(s[i]);
}
}
}
int main(){
std::ios::sync_with_stdio(false);
string word,str;
getline(cin,word);
getline(cin,str);
word = ' ' + word + ' '; // 为了好查找
str = ' ' + str + ' ';
XCHG(word);
XCHG(str);
int h = str.find(word);
int pos = h,num = 0;
if(h == str.npos){
cout << -1 << endl;
return 0;
}
while(h != str.npos){
num++;
if(h+word.length() >= str.length()) break;
h = str.find(word,h+1); // 此处是关键
}
cout << num << " " << pos << endl;
return 0;
}
知识积累
#include
- int tolower(int c);
- int toupper(int c);
转换为小写/大写
string::find()
size_type find (const value_type* _Ptr , size_type _Off = 0) const;
//find()函数的第1个参数是被搜索的字符串,第2个参数是在源串中开始搜索的下标位置
P1125 笨小猴
题目描述
笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!
这种方法的具体描述如下:假设 maxn \text{maxn} maxn 是单词中出现次数最多的字母的出现次数, minn \text{minn} minn 是单词中出现次数最少的字母的出现次数,如果 maxn − minn \text{maxn}-\text{minn} maxn−minn 是一个质数,那么笨小猴就认为这是个 Lucky Word,这样的单词很可能就是正确的答案。
输入格式
一个单词,其中只可能出现小写字母,并且长度小于 100 100 100。
输出格式
共两行,第一行是一个字符串,假设输入的的单词是 Lucky Word,那么输出 Lucky Word
,否则输出 No Answer
;
第二行是一个整数,如果输入单词是 Lucky Word
,输出
maxn
−
minn
\text{maxn}-\text{minn}
maxn−minn 的值,否则输出
0
0
0。
样例 #1
样例输入 #1
error
样例输出 #1
Lucky Word
2
样例 #2
样例输入 #2
olympic
样例输出 #2
No Answer
0
提示
【输入输出样例 1 解释】
单词 error
中出现最多的字母
r
\texttt r
r 出现了
3
3
3 次,出现次数最少的字母出现了
1
1
1 次,
3
−
1
=
2
3-1=2
3−1=2,
2
2
2 是质数。
【输入输出样例 2 解释】
单词 olympic
中出现最多的字母
i
\texttt i
i 出现了
1
1
1 次,出现次数最少的字母出现了
1
1
1 次,
1
−
1
=
0
1-1=0
1−1=0,
0
0
0 不是质数。
(本处原题面错误已经修正)
noip2008 提高第一题
我的代码
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
bool is_su(int A){
if(A < 2) return false;
for(int i = 2; i <= A/2; i++){
if(i != A && A%i==0){
return false;
}
}
return true;
}
int main(){
string str;
int term_NUM[30],maxn = -1,minn=0x3fffffff;
memset(term_NUM,0,30*sizeof(int));
cin >> str;
for(int i = 0; i < str.length(); i++){
term_NUM[str[i] - 'a']++;
}
for(int i = 0; i < 30; i++){
if(term_NUM[i] > maxn && term_NUM[i] >= 1)
maxn = term_NUM[i];
if(term_NUM[i] < minn && term_NUM[i] >= 1){
minn = term_NUM[i];
}
}
int ans = maxn - minn;
if(!is_su(ans)){
cout << "No Answer" << endl << 0 << endl;
}
else{
cout << "Lucky Word" << endl << ans << endl;
}
return 0;
}
在代码的编写中出现一下问题:
- memset()使用前需要添加头文件<string.h>
- memset的第三个参数指的是字节数,如果是int型,需要N×sizeof(int)
- 何为素数?
- 质数是指除了1和它本身以外,没有其他正整数能够整除它的数。例如,2、3、5、7、11、13等都是质数,而4、6、8、9等就不是质数。
141.环形链表(双指针、快慢指针)
题目描述
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是
[0, 104]
105 <= Node.val <= 105
pos
为1
或者链表中的一个 有效索引 。
我的代码
代码1:用map(但是map是有序的,每次加入都会进行排序)
class Solution {
public:
bool hasCycle(ListNode *head) {
map<ListNode *,int> A;
if(head == NULL) return false;
ListNode *p = head;
while(p){
if(A.find(p->next) != A.end()){
return true;
}
A[p->next] = p->val;
p = p->next;
}
return false;
}
};
代码2:因此想到用onordered_map也就是hashmap
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_map<ListNode *,int> A;
if(head == NULL) return false;
ListNode *p = head;
while(p){
if(A.find(p->next) != A.end()){
return true;
}
A[p->next] = p->val;
p = p->next;
}
return false;
}
};
代码3:由于hash表的空间复杂度为O(n),时间复杂度也为O(n)。为了提升空间复杂度,想到了快慢指针的方法
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *fast,*slow;
if(head == NULL || head->next == NULL) return false;
fast = head->next;
slow = head;
while(fast != slow){
if(fast == NULL || slow == NULL || fast->next == NULL) return false;
fast = fast->next->next;
slow = slow->next;
}
return true;
}
};
此处要注意:fast的前进速度要与slow不一样才能相遇;fast 和 fast→next为空都要判断
17.电话号码的字母组合
题目描述
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
提示:
0 <= digits.length <= 4
digits[i]
是范围['2', '9']
的一个数字。
我的代码
代码1:暴力求解
class Solution {
public:
vector<string> letterCombinations(string digits) {
vector<string> NUM_CHAR(10);
vector<string> ans;
NUM_CHAR[2] = "abc";
NUM_CHAR[3] = "def";
NUM_CHAR[4] = "ghi";
NUM_CHAR[5] = "jkl";
NUM_CHAR[6] = "mno";
NUM_CHAR[7] = "pqrs";
NUM_CHAR[8] = "tuv";
NUM_CHAR[9] = "wxyz";
if(digits.length() == 0) return ans;
else if(digits.length() == 1){
for(int i = 0;i < NUM_CHAR[digits[0] - '0'].length();i++){
string str;
str.push_back(NUM_CHAR[digits[0] - '0'][i]) ;
ans.push_back(str);
}
}
else if(digits.length() == 2){
for(int i = 0;i < NUM_CHAR[digits[0] - '0'].length();i++){
for(int j = 0;j < NUM_CHAR[digits[1] - '0'].length();j++){
string str;
str.push_back(NUM_CHAR[digits[0] - '0'][i]) ;
str.push_back(NUM_CHAR[digits[1] - '0'][j]) ;
ans.push_back(str);
}
}
}
else if(digits.length() == 3){
for(int i = 0;i < NUM_CHAR[digits[0] - '0'].length();i++){
for(int j = 0;j < NUM_CHAR[digits[1] - '0'].length();j++){
for(int x = 0; x < NUM_CHAR[digits[2] - '0'].length();x++){
string str;
str.push_back(NUM_CHAR[digits[0] - '0'][i]) ;
str.push_back(NUM_CHAR[digits[1] - '0'][j]) ;
str.push_back(NUM_CHAR[digits[2] - '0'][x]) ;
ans.push_back(str);
}
}
}
}
else if(digits.length() == 4){
for(int i = 0;i < NUM_CHAR[digits[0] - '0'].length();i++){
for(int j = 0;j < NUM_CHAR[digits[1] - '0'].length();j++){
for(int x = 0; x < NUM_CHAR[digits[2] - '0'].length();x++){
for(int y = 0; y < NUM_CHAR[digits[3] - '0'].length();y++){
string str;
str.push_back(NUM_CHAR[digits[0] - '0'][i]) ;
str.push_back(NUM_CHAR[digits[1] - '0'][j]) ;
str.push_back(NUM_CHAR[digits[2] - '0'][x]) ;
str.push_back(NUM_CHAR[digits[3] - '0'][y]) ;
ans.push_back(str);
}
}
}
}
}
return ans;
}
};
代码2:此处可以用回溯法,也就是dfs对vector进行构造
注意:代码1中的vector的初始化可以简化为代码2中的形式,其次,还可以用unordered_map来存储,然后用at()函数来查询’2’对应的字符串‘abc’
class Solution {
public:
vector<string> ans;
string temp;
vector<string> NUM_CHAR = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
void trackback(int index, string digits){
if(index == digits.length()){
ans.push_back(temp);
}
else{
for(int i = 0;i < NUM_CHAR[digits[index]-'0'].length(); i++){
temp.push_back(NUM_CHAR[digits[index]-'0'][i]);
trackback(index+1,digits);
temp.pop_back(); // 回溯
}
}
}
vector<string> letterCombinations(string digits) {
if(digits.length() == 0) return ans;
else{
trackback(0,digits);
return ans;
}
}
};
P2240 部分背包问题
题目描述
阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N ( N ≤ 100 ) N(N \le 100) N(N≤100) 堆金币,第 i i i 堆金币的总重量和总价值分别是 m i , v i ( 1 ≤ m i , v i ≤ 100 ) m_i,v_i(1\le m_i,v_i \le 100) mi,vi(1≤mi,vi≤100)。阿里巴巴有一个承重量为 T ( T ≤ 1000 ) T(T \le 1000) T(T≤1000) 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?
输入格式
第一行两个整数 N , T N,T N,T。
接下来 N N N 行,每行两个整数 m i , v i m_i,v_i mi,vi。
输出格式
一个实数表示答案,输出两位小数
样例 #1
样例输入 #1
4 50
10 60
20 100
30 120
15 45
样例输出 #1
240.00
我的代码
本题目的难点在于分析出:贪心,按平均价值排序
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<double> ans;
bool cmp(vector<double>A,vector<double>B){
return A[1]*B[0] > B[1]*A[0];
}
int main(){
int N;
double T,sum = 0;
double value = 0;
cin >> N >> T;
vector<vector<double> > gold(N,vector<double>(2));
for(int i = 0; i < N; i++){
cin >> gold[i][0] >> gold[i][1];
}
sort(gold.begin(),gold.end(),cmp);
for(int i = 0; i < gold.size() && sum < T; i++){
if(gold[i][0] > (T-sum)){
//sum = T;
value += gold[i][1]/gold[i][0] * (T-sum);
sum = T;
}
else{
sum += gold[i][0];
value += gold[i][1];
}
}
printf("%.2lf",value);
}
知识积累
min_element()和max_element()的头文件是algorithm
P1219 八皇后(DFS)
题目描述
一个如下的6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 2 4 6 1 3 52 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6 1 2 3 4 5 6
列号 2 4 6 1 3 5 2 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。并把它们以上面的序列方法输出,解按字典顺序排列。请输出前 33 个解。最后一行是解的总个数。
输入格式
一行一个正整数 n,表示棋盘是 n×n大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
样例 #1
样例输入 #1
6
样例输出 #1
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
提示
【数据范围】
对于 100% 的数据,6 ≤ 13。
题目翻译来自NOCOW。
USACO Training Section 1.5
我的代码
代码1:最开始想到的是深度优先搜索,遍历所有可能的情况,但是由于每次遍历都要对合法性进行判断,且判断过程十分复杂,因此最后的测试样例Time Out。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct point{
int x;
int y;
bool operator ==(const struct point that){
return (this->x == that.x) && (this->y == that.y);
}
};
vector<vector<struct point> > ans;
void dfs(vector<struct point> &temp, int index,int n){
if(index == n){
ans.push_back(temp);
return ;
}
for(int i = 0; i < n; i++){//在第index行上遍历每一列
struct point p1,p2;
bool flag = 0; // 标记是否非法
for(int j = 0; j < n; j++){
struct point p1,p2;
p1.x = j, p1.y = i;
p2.x = index,p2.y = i;
if(find(temp.begin(),temp.end(),p1) != temp.end() || find(temp.begin(),temp.end(),p2) != temp.end()){
flag = true;
break;
}
}
int ii = index,jj = i;
while(ii && jj && !flag){
p1.x = --ii,p1.y = --jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index, jj = i;
while(ii < n && jj && !flag){
p1.x = ++ii,p1.y = --jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index,jj = i;
while(ii && jj < n && !flag){
p1.x = --ii,p1.y = ++jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index, jj = i;
while(ii < n && jj < n && !flag){
p1.x = ++ii,p1.y = ++jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
if(!flag){
p1.x = index,p1.y = i;
temp.push_back(p1);
dfs(temp,index+1,n);
temp.pop_back();
}
}
}
bool cmp(struct point A, struct point B){
return A.x < B.x;
}
int main(){
int n;
cin >> n;
vector<struct point> temp;
dfs(temp,0,n);
for(int i = 0; i < ans.size(); i++){
sort(ans[i].begin(),ans[i].end(), cmp);
}
for(int i = 0; i < 3; i++){
for(int j = 0; j < ans[i].size(); j++){
cout << ans[i][j].y+1 << ' ';
}
cout << endl;
}
cout << ans.size() << endl;
return 0;
}
代码2:八皇后问题有比较简便的判断合法方式八皇后
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int> > ans;
vector<int> row(1000,0);
vector<int> column(1000,0);
vector<int> ls(1000,0);//向左斜
vector<int> rs(1000,0);//向右斜
void dfs(int index,int n, vector<int> &temp){
if(index == n){
ans.push_back(temp);
return ;
}
else{
for(int j = 0; j < n; j++){
if(!row[index] && !column[j] && !ls[index+j] && !rs[j-index+n]){
temp.push_back(j);
row[index] = column[j] = ls[index+j] = rs[j-index+n] = 1;
dfs(index+1,n,temp);
temp.pop_back(); //回溯
row[index] = column[j] = ls[index+j] = rs[j-index+n] = 0; // 回溯
}
}
}
}
int main(){
int n;
cin >> n;
vector<int> temp;
dfs(0,n,temp);
for(int i = 0; i < 3; i++){
for(int j = 0; j < ans[i].size(); j++){
cout << ans[i][j]+1 << ' ';
}
cout << endl;
}
cout << ans.size() << endl;
return 0;
}
P1090 合并果子(哈夫曼树)
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。
输入格式
共两行。第一行是一个整数 n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 ai(1≤ai≤20000) 是第 i 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 231231 。
我的代码
代码1:该题类似于哈夫曼编码,且是贪心算法。因此,就有了以下代码
#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int n,ans = 0;
cin >> n;
struct Tree *T;
vector<int> points(n,0x3fffffff);
for(int i = 0; i < n; i++){
cin >> points[i];
}
int i = n;
if(n == 1) {
cout << points[0] << endl;
return 0;
}
do{
sort(points.begin(),points.end());
int temp = points[0] + points[1];
ans += temp;
points.erase(points.begin(),points.begin()+2);
points.push_back(temp);
}while(points.size() > 1);
cout << ans << endl;
return 0;
}
代码2:但是当数据过大时,sort()函数的调用会影响运行时间,因此,此时要考虑用堆排序,此处可利用STL中的优先队列来快速实现小根堆。优先队列
#include<iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int main(){
int n,ans = 0;
cin >> n;
struct Tree *T;
//vector<int> points(n,0x3fffffff);
priority_queue<int,vector<int>,greater<int> > points;
for(int i = 0; i < n; i++){
int x;
cin >>x;
points.push(x);
}
int i = n;
if(n == 1) {
cout << points.top() << endl;
return 0;
}
while(points.size() > 1){
int x = points.top();
points.pop();
int y = points.top();
points.pop();
points.push(x+y);
ans += (x+y);
}
cout << ans << endl;
return 0;
}
43.字符串相乘(模拟)
题目描述
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
**注意:**不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
提示:
1 <= num1.length, num2.length <= 200
num1
和num2
只能由数字组成。num1
和num2
都不包含任何前导零,除了数字0本身。
我的代码
本题就是很简单的思路:模拟竖式乘法,但是我却没有按时写出来。下面附上代码
class Solution {
public:
string add_num(string num1,string num2){
string ans;
reverse(num1.begin(),num1.end());
reverse(num2.begin(),num2.end());
int ci = 0,i = 0,j = 0;
while(i < num1.length() && j < num2.length()){
ans.push_back(((num1[i]-'0')+(num2[j]-'0') + ci)%10 + '0');
ci = ((num1[i]-'0')+(num2[j]-'0') + ci)/10;
i++;
j++;
}
while(i < num1.length()){
ans.push_back(((num1[i]-'0')+ ci)%10 + '0');
ci = ((num1[i]-'0')+ ci)/10;
i++;
}
while(j < num2.length()){
ans.push_back(((num2[j]-'0') + ci)%10 + '0');
ci = ((num2[j]-'0') + ci)/10;
j++;
}
while(ci){
ans.push_back(ci%10 + '0');
ci = ci/10;
}
reverse(num1.begin(),num1.end());
reverse(num2.begin(),num2.end());
reverse(ans.begin(),ans.end());
return ans;
}
string multiply(string num1, string num2) {
string ans,temp;
int L1 = num1.length(),L2 = num2.length();
int j = L2-1;
if(num1 == "0" || num2 == "0") return "0";
while(j >= 0){
for(int i = L2-1; i > j; i--){ // 模拟竖式乘法,十位则加1个0,百位加2个0
temp.push_back('0');
}
int ci = 0;
for(int i = L1-1; i >= 0; i--){
temp.push_back(((num1[i]-'0')*(num2[j]-'0') + ci)%10 + '0');
ci = ((num1[i]-'0')*(num2[j]-'0') + ci)/10;
}
while(ci){
temp.push_back(ci%10 + '0');
ci = ci/10;
}
reverse(temp.begin(),temp.end());
ans = add_num(ans,temp);
temp.clear();
j--;
}
return ans;
}
};
71.简化路径(deque)堆栈
题目描述
给你一个字符串 path
,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/'
开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//'
)都被视为单个斜杠 '/'
。 对于此问题,任何其他格式的点(例如,'...'
)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
- 始终以斜杠
'/'
开头。 - 两个目录名之间必须只有一个斜杠
'/'
。 - 最后一个目录名(如果存在)不能 以
'/'
结尾。 - 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含
'.'
或'..'
)。
返回简化后得到的 规范路径 。
示例 1:
输入:path = "/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:path = "/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。
示例 3:
输入:path = "/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:path = "/a/./b/../../c/"
输出:"/c"
提示:
1 <= path.length <= 3000
path
由英文字母,数字,'.'
,'/'
或'_'
组成。path
是一个有效的 Unix 风格绝对路径。
我的代码
其实本题思路很清楚,用栈来保存路径,就是将//中的字符串取出来,然后判断其
1.temp字符串=“…”,则需要出栈
2.temp字符串为空或者’.’,则不需要处理
3.temp字符串为路径名,则入栈
解此类问题,一定要有一个清晰的思路,其实不是特别难。此处用deque来实现,deque是一个双向栈。
class Solution {
public:
string simplifyPath(string path) {
deque<string> names;
string temp,ans;
int i = 0,n = path.length();
while(i < n){
if(path[i] == '/'){
if(!temp.empty()){
names.push_back(temp);
temp.clear();
}
i++;
}
else{
while(i < n && path[i] != '/'){
temp.push_back(path[i++]);
}
if(temp == ".."){
if(!names.empty())
names.pop_back();
temp.clear();
}
else if(temp == ".") {
i++;
temp.clear();
}
}
}
if(!temp.empty()) names.push_back(temp);
if(names.empty()) return "/";
while(!names.empty()){
ans += "/";
ans += names.front();
names.pop_front();
}
return ans;
}
};
98. 验证二叉搜索树
题目描述
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 104]
内 231 <= Node.val <= 231 - 1
我的代码
本题目要知道二叉排序数,左边所有的要小于根节点,右边所有的要大于根节点,因此,需要一个范围。代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isValid(TreeNode* root, long lower, long upper){
if(root == nullptr ) return true;
if(!(isValid(root->left,lower,root->val) == true && isValid(root->right,root->val,upper) == true)) return false;
else {
if(root->val > lower && root->val < upper){
return true;
}
else return false;
}
}
bool isValidBST(TreeNode* root) {
return isValid(root,LONG_MIN,LONG_MAX);
}
};
注:此处的LONG_MIN与LONG_MAX包含在头文件<limits.h>文件中。
153. 寻找旋转排序数组中的最小值(双指针)
题目描述
已知一个长度为n的数组,预先按照升序排列,经由1到n此旋转后,得到输入数组。例如,原数组
nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
n == nums.length
1 <= n <= 5000
5000 <= nums[i] <= 5000
nums
中的所有整数 互不相同nums
原来是一个升序排序的数组,并进行了1
至n
次旋转
我的代码
代码1:由于经过交换,要么nums任为升序,要么,nums呈现如下方式
因此,本题想到用双指针,一个从左边开始找,一个从右边开始找,找到一个不符合升序和降序的位置即可。代码如下:
class Solution {
public:
int findMin(vector<int>& nums) {
//nums.pop_front();
int n = nums.size(),i = 0, j = n-1;
if(nums[0] <= nums[n-1]) return nums[0];
while(nums[i] < nums[i+1] && nums[j] > nums[j-1] && i < j){
i++;
j--;
}
if(nums[i] > nums[i+1]) return nums[i+1];
if(nums[j] < nums[j-1]) return nums[j];
return 1;
}
};
但是,该算法并不符合题目要求,最坏情况下要遍历整个表,也即目标位置位于数组中央时。此时的时间复杂度为O(n)。因此就有了以下二分法的思路
代码2:采用二分法的思路,进行查找:
class Solution {
public:
int findMin(vector<int>& nums) {
int pilot ,low = 0, hight = nums.size()-1;
while(low < hight){
pilot = (low+hight)/2;
if(nums[pilot] < nums[hight]){
hight = pilot;
}
else{
low = pilot+1;
}
}
return nums[low];
}
};
227. 基本计算器 II
题目描述
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1]
的范围内。
**注意:**不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
提示:
1 <= s.length <= 3 * 105
s
由整数和算符('+', '-', '*', '/')
组成,中间由一些空格隔开s
表示一个 有效表达式- 表达式中的所有整数都是非负整数,且在范围
[0, 231 - 1]
内 - 题目数据保证答案是一个 32-bit 整数
我的代码
代码1:本题需要考虑以下几个问题:
- 运算符的优先级
- 什么时候将运算符出栈并计算?
- 什么时候将操作数进栈?
- 如何进行优先级的运算?
代码思路:利用哈希表来存储运算符的优先级对比矩阵,当栈顶操作符优先级高于此操作符时,优先计算栈顶表达式,否则则操作符入栈。矩阵初始化如下,1代表优先级高,-1代表优先级低:
unordered_map<string,int> map_ops = { {"++",1},{"+-",1},{"+*",-1},{"+/",-1},
{"-+",1},{"--",1},{"-*",-1},{"-/",-1},
{"*+",1},{"*-",1},{"**",1},{"*/",1},
{"/+",1},{"/-",1},{"/*",1},{"//",1} };
完整代码如下:
#include <iostream>
#include <string>
#include <stack>
#include <unordered_map>
using namespace std;
int calculate(string s) {
unordered_map<string,int> map_ops = { {"++",1},{"+-",1},{"+*",-1},{"+/",-1},
{"-+",1},{"--",1},{"-*",-1},{"-/",-1},
{"*+",1},{"*-",1},{"**",1},{"*/",1},
{"/+",1},{"/-",1},{"/*",1},{"//",1} };
int ans = 0,temp = 0;
stack<string> ops;
stack<int> nums;
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])){
temp = temp *10 + (s[i] - '0');
}
else if(s[i] == ' '){ // 跳过空格
continue;
}
else {
nums.push(temp);
//cout << "push,temp:" << temp << endl;
temp = 0; // temp置零
if(ops.empty()) { // 操作符栈中没有操作符, 直接入栈
string ss;
ss.push_back(s[i]);
ops.push(ss);
continue;
}
string ss = ops.top() + s[i];
cout << "ss:" << ss << endl;
if(map_ops.at(ss) == 1){
// 依次将栈中所有优先级大于s[i]的操作符计算
while(map_ops.at(ss) == 1 && !ops.empty()){
int B = nums.top();
nums.pop();
int A = nums.top();
nums.pop();
string t = ops.top();
ops.pop();
if(!ops.empty())ss = ops.top() + s[i];
switch(t[0]){
case '+':
nums.push(A+B);
cout << A << '+' << B << endl;
break;
case '-':
nums.push(A-B);
cout << A << '-' << B << endl;
break;
case '*':
nums.push(A*B);
cout << A << '*' << B << endl;
break;
case '/':
nums.push(A/B);
cout << A << '/' << B << endl;
}
}
}
string ss;
// 操作符进栈
ss.push_back(s[i]);
ops.push(ss);
}
}
nums.push(temp);
while(!ops.empty()){
string ss = ops.top();
ops.pop();
int B = nums.top();
nums.pop();
int A = nums.top();
nums.pop();
switch(ss[0]){
case '+':
nums.push(A+B);
cout << A << '+' << B << endl;
break;
case '-':
nums.push(A-B);
cout << A << '-' << B << endl;
break;
case '*':
nums.push(A*B);
cout << A << '*' << B << endl;
break;
case '/':
nums.push(A/B);
cout << A << '/' << B << endl;
}
}
if(nums.empty()) return 0;
else return nums.top();
}
int main(){
string str;
cin >> str;
cout << calculate(str) << endl;
return 0;
}
但是此代码很长,因此找到以下优化代码:
class Solution {
public:
stack<int> num; //存贮数字
stack<char> op; //存贮操作
void eval()
{
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
int r;
if (c == '+') r = a + b;
else if (c == '-') r = a - b;
else if (c == '*') r = a * b;
else r = a / b;
num.push(r);
}
int calculate(string s) {
s = '0' + s; // 对开头是负数的处理
unordered_map<char, int> pr;
pr['+'] = pr['-'] = 1, pr['*'] = pr['/'] = 2; //定义运算符的优先级
for(int i = 0; i < s.size(); i++)
{
char c = s[i];
if(c == ' ') continue; //跳过空格
if(isdigit(c)) //c是数字,读取一个连续的数字
{
int x= 0;
while(i < s.size() && s[i] >= '0' && s[i] <= '9') x = x * 10 + (s[i++] - '0'); num.push(x);
i--;
}
else //c是操作符
{ //op栈非空并且栈顶操作符优先级大于等于当前操作符c的优先级,进行eval()计算
while(op.size() && pr[op.top()] >= pr[c]) eval();
op.push(c);
}
}
while(op.size()) eval();
return num.top();
}
};
此代码有一个很好的点,那就是模块化,当一个计算多次出现时,将其写入和一个函数,进行函数调用即可。同时,对于优先级的处理,其直接用比较即可,比我的代码要好很多pr[op.top()] >= pr[c]。
97. 交错字符串(动态规划)
题目描述
给定三个字符串 s1
、s2
、s3
,请你帮忙验证 s3
是否是由 s1
和 s2
**交错 组成的。
两个字符串 s
和 t
交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
- 交错 是
s1 + t1 + s2 + t2 + s3 + t3 + ...
或者t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b
意味着字符串 a
和 b
连接。
示例 1:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出:true
示例 2:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false
示例 3:
输入:s1 = "", s2 = "", s3 = ""
输出:true
提示:
0 <= s1.length, s2.length <= 100
0 <= s3.length <= 200
s1
、s2
、和s3
都由小写英文字母组成
我的代码
代码1:首先我想到了双指针,即依次匹配s1、s2,但是这忽略了一种情况,当s1等于aabc,s2等于abb,s3等于aabbabc时,正确结果为True但结果返回false。因此这种方法应该pass掉
代码2:本题需要用到动态规划的思想:
#include <iostream>
#include <vector>
using namespace std;
bool isInterleave(string s1, string s2, string s3) {
int n1 = s1.length(), n2 = s2.length(),n3 = s3.length();
if(n1+n2 != n3) return false;
vector< vector <int> > dp(n1+1,vector<int>(n2+1,0));
dp[0][0] = 1;
for(int i =0; i <= n1; i++){
for(int j = 0; j <= n2; j++){
int p = i+j-1;
if(i > 0){
dp[i][j] |= (s1[i-1] == s3[p] && dp[i-1][j]);
}// |运算是因为上下两种情况只要一种情况为true,那么最终结果就为True
if(j > 0){
dp[i][j] |= (s2[j-1] == s3[p] && dp[i][j-1]);
}
}
}
if(dp[n1][n2] == 1) return true;
else return false;
}
int main(){
string s1,s2,s3;
cin >> s1 >> s2 >> s3;
cout << isInterleave(s1,s2,s3);
return 0;
}
剑指 Offer 46. 把数字翻译成字符串(动态规划)
题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出:5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
我的代码
要不是有提示动态规划,我可能想不到要用动态规划。动态规划思路如下:
- 设dp[i]代表从0开始到下标为i的字符串能够翻译的种数
- dp[i] 与dp[i-1]和dp[i-2]有关,首先num对应的第i位一定可以翻译为某个字母,因此dp[i] = dp[i-1]+1,其次,如果num第i位和第i-1位组成的数字小于26且大于9时,又可以得到另一种翻译,此时dp[i] += dp[i-2]。为什么要大于9呢?因为如果num = 1002,那么对于02来说,其不能翻译成一个字母,只能翻译成两个字母。
代码如下:
class Solution {
public:
vector<int> itostr(int num){
vector<int> ans;
while(num){
ans.push_back(num%10);
num /= 10;
}
reverse(ans.begin(),ans.end());
return ans;
}
int translateNum(int num) {
if(num == 0) return 1;
vector<int> nums = itostr(num);
int n = nums.size();
vector<int> dp(n,0);
dp[0] = 1;
for(int i = 1; i < n; i++){
if(nums[i-1]*10 + nums[i] <= 25 && nums[i-1]*10 + nums[i] >= 10){
if(i>1) dp[i] += dp[i-2];
else dp[i]++;
}
dp[i] += dp[i-1];
}
return dp[n-1];
}
};
32. 最长有效括号(动态规划)
题目描述
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104
s[i]
为'('
或')'
我的代码
其实,最开始我的思路是对的,我第一反应就是用动态规划,但是由于我没有考虑全面,因此,动态规划做出来是错误的,此外,我也没有真正理解题目的意思。我以为只求连续的()括号,然而我并没有考虑到求(())括号嵌套的情况。
法一:动态规划
这是一个 最值型 动态规划的题目。
动态规划题目分析的 4 个步骤:
- 确定状态
研究最优策略的最后一步 - 化为子问题
转移方程
根据子问题定义得到 - 初始条件和边界情况
计算顺序
基于上图分析即可得出动态规划
具体实现 首先,**我们定义一个 dp 数组,其中第 i 个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。**当i为 ')'时分析对于位置括号情况即可。
具体代码如下:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if(n == 0) return 0;
vector<int> dp(n+1,0);
//memset(dp,0,sizeof(dp));
int max_ = 0;
for(int i = 1; i < n; i++){
if(s[i] == ')'){
if(s[i-1] == '('){
dp[i] = i>1? (dp[i-2] + 2) : 2;
}
else if(s[i-1] == ')'){
if(i-dp[i-1]-1 >= 0 && s[i-dp[i-1]-1] == '('){
dp[i] = i-dp[i-1]-2 >= 0? dp[i-1] + 2 + dp[i-dp[i-1]-2] : dp[i-1] + 2;
}
}
cout << "dp[" << i << "]: " << dp[i] << endl;
}
max_ = max(max_,dp[i]);
}
return max_;
}
};
法二:栈
474. 一和零(动态规划)
题目描述
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的 子集 。
示例 1:
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
提示:
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i]
仅由'0'
和'1'
组成1 <= m, n <= 100
我的代码
思路:本题是一道动态规划问题,不过是01背包问题的升级版
class Solution {
public:
void get_num(string str, int &n1, int &n2){ // 获取0和1的数量
for(int i = 0; i < str.length(); i++){
if(str[i] == '1') n2++;
else n1++;
}
}
int findMaxForm(vector<string>& strs, int m, int n) {
int dp[strs.size()+1][m+1][n+1];
memset(dp,0,sizeof(dp));
// dp[l][i][j] 表示长度为l的字符串数组,0的数量最多为i,1的数量最多为j时,最长的子集长度
for(int l = 1; l <= strs.size(); l++){
for(int i = 0; i <= m; i++){
for(int j = 0; j <= n; j++){
int n0 = 0,n1= 0;
get_num(strs[l-1],n0,n1);
if(n0 > i || n1 > j) dp[l][i][j] = dp[l-1][i][j]; // 当第l个字符串不能加入时
else if(i >= n0 && j >= n1){ // 第1个字符串可以加入时
dp[l][i][j] = max(dp[l-1][i-n0][j-n1]+1,dp[l-1][i][j]);
}
}
}
}
return dp[strs.size()][m][n];
}
};
1626. 无矛盾的最佳球队(动态规划)
题目描述
假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。
然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 没有矛盾 的球队。如果一名年龄较小球员的分数 严格大于 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。
给你两个列表 scores
和 ages
,其中每组 scores[i]
和 ages[i]
表示第 i
名球员的分数和年龄。请你返回 所有可能的无矛盾球队中得分最高那支的分数 。
示例 1:
输入:scores = [1,3,5,10,15], ages = [1,2,3,4,5]
输出:34
解释:你可以选中所有球员。
示例 2:
输入:scores = [4,5,6,5], ages = [2,1,2,1]
输出:16
解释:最佳的选择是后 3 名球员。注意,你可以选中多个同龄球员。
示例 3:
输入:scores = [1,2,3,5], ages = [8,9,10,1]
输出:6
解释:最佳的选择是前 3 名球员。
提示:
1 <= scores.length, ages.length <= 1000
scores.length == ages.length
1 <= scores[i] <= 106
1 <= ages[i] <= 1000
我的代码
我的代码思路很简单,就是用动态规划,dp[i]表示从0到i这几个球员得分最高值
- 首先要对这几个球员进行排序,把年龄小的放前边,年龄相同,则将成绩小的放前边。由于已经将年龄从小到大排好序了,因此,在计算时,可以不用考虑主要年龄了。
- dp[i] 有以下几种情况:
- 第i个同学不与前面的队员组成球队,自成一队
- 第i个队员与前边的某些人组成球队
- dp[i] 需要取这些值里边的最大值。
代码如下:
class Solution {
public:
struct people{
int score;
int age;
};
static bool cmp(struct people A, struct people B){
return A.age == B.age? A.score < B.score: A.age < B.age;
}
int bestTeamScore(vector<int>& scores, vector<int>& ages) {
int n = ages.size(),ans = 0;
int dp[n];
memset(dp,0,sizeof(dp));
vector<struct people> peo(n);
for(int i = 0; i < n; i++){
peo[i].score = scores[i];
peo[i].age = ages[i];
}
// 排序
sort(peo.begin(),peo.end(),cmp);
for(int i = 0; i < n; i++){
// 初始化dp[i] ,也即每个人都自成一队的情况
dp[i] = peo[i].score;
// 每次更新dp[i],都要更新最大值ans
ans = max(ans,dp[i]);
}
for(int i = 1; i < n; i++){
for(int j = i-1; j >= 0; j--){
// 遍历第i个队员前的所有人,找出最大值存入dp[i]
if(peo[j].score <= peo[i].score){
dp[i] = max(dp[i],dp[j] + peo[i].score);
}
}
ans = max(ans,dp[i]);
}
return ans;
}
};
139. 单词拆分(动态规划)
题目描述
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为"applepenapple" 可以由"apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅有小写英文字母组成wordDict
中的所有字符串 互不相同
我的代码
我首先想到的是深度优先,通过遍历所有的可能性,然后遇到True则返回结果。但是可想而知,当字典数目太大时,时间复杂度会特别高。因此没有AC
错误代码如下:
class Solution {
public:
bool ans = false;
void dfs(string s, vector<string> wordDict, bool& flag,int len){
if(len <= 0){
ans = true;
flag = true;
return ;
}
else{
for(int i = 0; i < wordDict.size() && !flag; i++){
if(!flag){
int x= s.find(wordDict[i]);
if(x != -1){
string ss = s.substr(0,x);
for(int j = 0; j < wordDict[i].size(); j++) ss+= " ";
ss += s.substr(x+wordDict[i].length(),s.length()-x-wordDict[i].length());
cout << ss << endl;
dfs(ss,wordDict,flag,len-wordDict[i].length());
}
}
}
}
}
bool wordBreak(string s, vector<string>& wordDict) {
bool flag = false;
dfs(s,wordDict,flag,s.length());
return ans;
}
};
而且,我本来的方法也不对,因为我的想法是,如果遇到匹配的单词,则简单的将单词从string中删除,进行下一步递归,但是我没有想到删掉之后会改变string的构造,因此不能简单删除,而是用“ ”空格来代替被匹配的子串。
代码2
本题主要考察动态规划,dp[i]代表字符串s的前i个字符可以由字典表示出来
dp[i]与前边的dp数组的关系如下:
上面的布局可以用序列 2 4 6 1 3 52 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6 1 2 3 4 5 6
列号 2 4 6 1 3 5 2 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。并把它们以上面的序列方法输出,解按字典顺序排列。请输出前 33 个解。最后一行是解的总个数。
输入格式
一行一个正整数 n,表示棋盘是 n×n大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
样例 #1
样例输入 #1
6
样例输出 #1
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
提示
【数据范围】
对于 100% 的数据,6 ≤ 13。
题目翻译来自NOCOW。
USACO Training Section 1.5
我的代码
代码1:最开始想到的是深度优先搜索,遍历所有可能的情况,但是由于每次遍历都要对合法性进行判断,且判断过程十分复杂,因此最后的测试样例Time Out。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct point{
int x;
int y;
bool operator ==(const struct point that){
return (this->x == that.x) && (this->y == that.y);
}
};
vector<vector<struct point> > ans;
void dfs(vector<struct point> &temp, int index,int n){
if(index == n){
ans.push_back(temp);
return ;
}
for(int i = 0; i < n; i++){//在第index行上遍历每一列
struct point p1,p2;
bool flag = 0; // 标记是否非法
for(int j = 0; j < n; j++){
struct point p1,p2;
p1.x = j, p1.y = i;
p2.x = index,p2.y = i;
if(find(temp.begin(),temp.end(),p1) != temp.end() || find(temp.begin(),temp.end(),p2) != temp.end()){
flag = true;
break;
}
}
int ii = index,jj = i;
while(ii && jj && !flag){
p1.x = --ii,p1.y = --jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index, jj = i;
while(ii < n && jj && !flag){
p1.x = ++ii,p1.y = --jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index,jj = i;
while(ii && jj < n && !flag){
p1.x = --ii,p1.y = ++jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
ii = index, jj = i;
while(ii < n && jj < n && !flag){
p1.x = ++ii,p1.y = ++jj;
if(find(temp.begin(),temp.end(),p1) != temp.end()){
flag = true;
}
}
if(!flag){
p1.x = index,p1.y = i;
temp.push_back(p1);
dfs(temp,index+1,n);
temp.pop_back();
}
}
}
bool cmp(struct point A, struct point B){
return A.x < B.x;
}
int main(){
int n;
cin >> n;
vector<struct point> temp;
dfs(temp,0,n);
for(int i = 0; i < ans.size(); i++){
sort(ans[i].begin(),ans[i].end(), cmp);
}
for(int i = 0; i < 3; i++){
for(int j = 0; j < ans[i].size(); j++){
cout << ans[i][j].y+1 << ' ';
}
cout << endl;
}
cout << ans.size() << endl;
return 0;
}
代码2:八皇后问题有比较简便的判断合法方式八皇后
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int> > ans;
vector<int> row(1000,0);
vector<int> column(1000,0);
vector<int> ls(1000,0);//向左斜
vector<int> rs(1000,0);//向右斜
void dfs(int index,int n, vector<int> &temp){
if(index == n){
ans.push_back(temp);
return ;
}
else{
for(int j = 0; j < n; j++){
if(!row[index] && !column[j] && !ls[index+j] && !rs[j-index+n]){
temp.push_back(j);
row[index] = column[j] = ls[index+j] = rs[j-index+n] = 1;
dfs(index+1,n,temp);
temp.pop_back(); //回溯
row[index] = column[j] = ls[index+j] = rs[j-index+n] = 0; // 回溯
}
}
}
}
int main(){
int n;
cin >> n;
vector<int> temp;
dfs(0,n,temp);
for(int i = 0; i < 3; i++){
for(int j = 0; j < ans[i].size(); j++){
cout << ans[i][j]+1 << ' ';
}
cout << endl;
}
cout << ans.size() << endl;
return 0;
}
P1090 合并果子(哈夫曼树)
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。
输入格式
共两行。第一行是一个整数 n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 ai(1≤ai≤20000) 是第 i 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 231231 。
我的代码
代码1:该题类似于哈夫曼编码,且是贪心算法。因此,就有了以下代码
#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int n,ans = 0;
cin >> n;
struct Tree *T;
vector<int> points(n,0x3fffffff);
for(int i = 0; i < n; i++){
cin >> points[i];
}
int i = n;
if(n == 1) {
cout << points[0] << endl;
return 0;
}
do{
sort(points.begin(),points.end());
int temp = points[0] + points[1];
ans += temp;
points.erase(points.begin(),points.begin()+2);
points.push_back(temp);
}while(points.size() > 1);
cout << ans << endl;
return 0;
}
代码2:但是当数据过大时,sort()函数的调用会影响运行时间,因此,此时要考虑用堆排序,此处可利用STL中的优先队列来快速实现小根堆。优先队列
#include<iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int main(){
int n,ans = 0;
cin >> n;
struct Tree *T;
//vector<int> points(n,0x3fffffff);
priority_queue<int,vector<int>,greater<int> > points;
for(int i = 0; i < n; i++){
int x;
cin >>x;
points.push(x);
}
int i = n;
if(n == 1) {
cout << points.top() << endl;
return 0;
}
while(points.size() > 1){
int x = points.top();
points.pop();
int y = points.top();
points.pop();
points.push(x+y);
ans += (x+y);
}
cout << ans << endl;
return 0;
}
43.字符串相乘(模拟)
题目描述
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
**注意:**不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
提示:
1 <= num1.length, num2.length <= 200
num1
和num2
只能由数字组成。num1
和num2
都不包含任何前导零,除了数字0本身。
我的代码
本题就是很简单的思路:模拟竖式乘法,但是我却没有按时写出来。下面附上代码
class Solution {
public:
string add_num(string num1,string num2){
string ans;
reverse(num1.begin(),num1.end());
reverse(num2.begin(),num2.end());
int ci = 0,源站ile(i < num1.length() && j < num2.length()){
ans.push_back(((num1[i]-'0')+(num2[j]-'0') + ci)%10 + '0');
ci = ((num1[i]-'0')+(num2[j]-'0') + ci)/10;
i++;
j++;
}
while(i < num1.length()){
ans.push_back(((num1[i]-'0')+ ci)%10 + '0');
ci = ((num1[i]-'0')+ ci)/10;
i++;
}
while(j < num2.length()){
ans.push_back(((num2[j]-'0') + ci)%10 + '0');
ci = ((num2[j]-'0') + ci)/10;
j++;
}
while(ci){
ans.push_back(ci%10 + '0');
ci = ci/10;
}
reverse(num1.begin(),num1.end());
reverse(num2.begin(),num2.end());
reverse(ans.begin(),ans.end());
return ans;
}
string multiply(string num1, string num2) {
string ans,temp;
int L1 = num1.length(),L2 = num2.length();
int j = L2-1;
if(num1 == "0" || num2 == "0") return "0";
while(j >= 0){
for(int i = L2-1; i > j; i--){ // 模拟竖式乘法,十位则加1个0,百位加2个0
temp.push_back('0');
}
int ci = 0;
for(int i = L1-1; i >= 0; i--){
temp.push_back(((num1[i]-'0')*(num2[j]-'0') + ci)%10 + '0');
ci = ((num1[i]-'0')*(num2[j]-'0') + ci)/10;
}
while(ci){
temp.push_back(ci%10 + '0');
ci = ci/10;
}
reverse(temp.begin(),temp.end());
ans = add_num(ans,temp);
temp.clear();
j--;
}
return ans;
}
};
71.简化路径(deque)堆栈
题目描述
给你一个字符串 path
,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/'
开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//'
)都被视为单个斜杠 '/'
。 对于此问题,任何其他格式的点(例如,'...'
)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
- 始终以斜杠
'/'
开头。 - 两个目录名之间必须只有一个斜杠
'/'
。 - 最后一个目录名(如果存在)不能 以
'/'
结尾。 - 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含
'.'
或'..'
)。
返回简化后得到的 规范路径 。
示例 1:
输入:path = "/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:path = "/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。
示例 3:
输入:path = "/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
**示例 4"/a/./b/…/…/c/"
输出:“/c”
**提示:**
- `1 <= path.length <= 3000`
- `path` 由英文字母,数字,`'.'`,`'/'` 或 `'_'` 组成。
- `path` 是一个有效的 Unix 风格绝对路径。
## 我的代码
其实本题思路很清楚,用栈来保存路径,就是将//中的字符串取出来,然后判断其
1.temp字符串=“..”,则需要出栈
2.temp字符串为空或者’.’,则不需要处理
3.temp字符串为路径名,则入栈
解此类问题,一定要有一个清晰的思路,其实不是特别难。此处用deque来实现,deque是一个双向栈。
```cpp
class Solution {
public:
string simplifyPath(string path) {
deque<string> names;
string temp,ans;
int i = 0,n = path.length();
while(i < n){
if(path[i] == '/'){
if(!temp.empty()){ names.push_back(temp);
temp.clear();
}
i++;
}
else{
while(i < n && path[i] != '/'){
temp.push_back(path[i++]);
}
if(temp == ".."){
if(!names.empty())
names.pop_back();
temp.clear();
}
else if(temp == ".") {
i++;
temp.clear()
}
}
if(!temp.empty()) names.push_back(temp);
if(names.empty()) return "/";
while(!names.empty()){
ans += "/";
ans += names.front();
names.pop_front();
}
return ans;
}
};
98. 验证二叉搜索树
题目描述
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 104]
内 231 <= Node.val <= 231 - 1
我的代码
本题目要知道二叉排序数,左边所有的要小于根节点,右边所有的要大于根节点,因此,需要一个范围。代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isValid(TreeNode* root, long lower, long upper){
if(root == nullptr ) return true;
if(!(isValid(root->left,lower,root->val) == true && isValid(root->right,root->val,upper) == true)) return false;
else {
if(root->val > lower && root->val < upper){
return true;
}
else return false;
}
}
bool isValidBST(TreeNode* root) {
return isValid(root,LONG_MIN,LONG_MAX);
}
};
注:此处的LONG_MIN与LONG_MAX包含在头文件<limits.h>文件中。
153. 寻找旋转排序数组中的最小值(双指针)
题目描述
已知一个长度为n的数组,预先按照升序排列,经由1到n此旋转后,得到输入数组。例如,原数组
nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
```:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
n == nums.length
1 <= n <= 5000
5000 <= nums[i] <= 5000
nums
中的所有整数 互不相同nums
原来是一个升序排序的数组,并进行了1
至n
次旋转
我的代码
代码1:由于经过交换,要么nums任为升序,要么,nums呈现如下方式
因此,本题想到用双指针,一个从左边开始找,一个从右边开始找,找到一个不符合升序和降序的位置即可。代码如下:
class Solution {
public:
int findMin(vector<int>& nums) {
//nums.pop_front();
int n = nums.size(),i = 0, j = n-1;
if(nums[0] <= nums[n-1]) return nums[0];
while(nums[i] < nums[i+1] && nums[j] > nums[j-1] && i < j){
i++;
j--;
}
if(nums[i] > nums[i+1]) return nums[i+1];
if(nums[j] < nums[j-1]) return nums[j];
return 1;
}
};
但是,该算法并不符合题目要求,最坏情况下要遍历整个表,也即目标位置位于数组中央时。此时的时间复杂度为O(n)。因此就有了以下二分法的思路
代码2:采用二分法的思路,进行查找:
class Solution {
public:
int findMin(vector<int>& nums) {
int pilot ,low = 0, hight = nums.size()-1;
while(low < hight){
pilot = (low+hight)/2;
if(nums[pilot] < nums[hight]){
hight = pilot;
}
else{
low = pilot+1;
}
}
return nums[low];
}
};
227. 基本计算器 II
题目描述
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1]
的范围内。
**注意:**不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
提示:
1 <= s.length <= 3 * 105
s
由整数和算符('+', '-', '*', '/')
组成,中间由一些空格隔开s
表示一个 有效表达式- 表达式中的所有整数都是非负整数,且在范围
[0, 231 - 1]
内 - 题目数据保证答案是一个 32-bit 整数
我的代码
代码1:本题需要考虑以下几个问题:
- 运算符的优先级
- 什么时候将运算符出栈并计算?
- 什么时候将操作数进栈?
- 如何进行优先级的运算?
代码思路:利用哈希表来存储运算符的优先级对比矩阵,当栈顶操作符优先级高于此操作符时,优先计算栈顶表达式,否则则操作符入栈。矩阵初始化如下,1代表优先级高,-1代表优先级低:
unordered_map<string,int> map_ops = { {"++",1},{"+-",1},{"+*",-1},{"+/",-1},
{"-+",1},{"--",1},{"-*",-1},{"-/",-1},
{"*+",1},{"*-",1},{"**",1},{"*/",1},
{"/+",1},{"/-",1},{"/*",1},{"//",1} };
完整代码如下:
#include <iostream>
#include <string>
#include <stack>
#include <unordered_map>
using namespace std;
int calculate(string s) {
unordered_map<string,int> map_ops = { {"++",1},{"+-",1},{"+*",-1},{"+/",-1},
{"-+",1},{"--",1},{"-*",-1},{"-/",-1},
{"*+",1},{"*-",1},{"**",1},{"*/",1},
{"/+",1},{"/-",1},{"/*",1},{"//",1} };
int ans = 0,temp = 0;
stack<string> ops;
stack<int> nums;
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])){
temp = temp *10 + (s[i] - '0');
}
else if(s[i] == ' '){ // 跳过空格
continue;
}
else {
nums.push(temp);
//cout << "push,temp:" << temp << endl;
temp = 0; // temp置零
if(ops.empty()) { // 操作符栈中没有操作符, 直接入栈
string ss;
ss.push_back(s[i]);
ops.push(ss);
continue;
}
string ss = ops.top() + s[i];
cout << "ss:" << ss << endl;
if(map_ops.at(ss) == 1){
// 依次将栈中所有优先级大于s[i]的操作符计算
while(map_ops.at(ss) == 1 && !ops.empty()){
int B = nums.top();
nums.pop();
int A = nums.top();
nums.pop();
string t = ops.top();
ops.pop();
if(!ops.empty())ss = ops.top() + s[i];
switch(t[0]){
case '+':
nums.push(A+B);
cout << A << '+' << B << endl;
break;
case '-':
nums.push(A-B);
cout << A << '-' << B << endl;
break;
case '*':
nums.push(A*B);
cout << A << '*' << B << endl;
break;
case '/':
nums.push(A/B);
cout << A << '/' << B << endl;
}
}
}
string ss;
// 操作符进栈
ss.push_back(s[i]);
ops.push(ss);
}
}
nums.push(temp);
while(!ops.empty()){
string ss = ops.top();
ops.pop();
int B = nums.top();
nums.pop();
int A = nums.top();
nums.pop();
switch(ss[0]){
case '+':
nums.push(A+B);
cout << A << '+' << B << endl;
break;
case '-':
nums.push(A-B);
cout << A << '-' << B << endl;
break;
case '*':
nums.push(A*B);
cout << A << '*' << B << endl;
break;
case '/':
nums.push(A/B);
cout << A << '/' << B << endl;
}
}
if(nums.empty()) return 0;
else return nums.top();
}
int main(){
string str;
cin >> str;
cout << calculate(str) << endl;
return 0;
}
但是此代码很长,因此找到以下优化代码:
class Solution {
public:
stack<int> num; //存贮数字
stack<char> op; //存贮操作
void eval()
{
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
int r;
if (c == '+') r = a + b;
else if (c == '-') r = a - b;
else if (c == '*') r = a * b;
else r = a / b;
num.push(r);
}
int calculate(string s) {
s = '0' + s; // 对开头是负数的处理
unordered_map<char, int> pr;
pr['+'] = pr['-'] = 1, pr['*'] = pr['/'] = 2; //定义运算符的优先级
for(int i = 0; i < s.size(); i++)
{
char c = s[i];
if(c == ' ') continue; //跳过空格
if(isdigit(c)) //c是数字,读取一个连续的数字
{
int x= 0;
while(i < s.size() && s[i] >= '0' && s[i] <= '9') x = x * 10 + (s[i++] - '0'); num.push(x);
i--;
}
else //c是操作符
{ //op栈非空并且栈顶操作符优先级大于等于当前操作符c的优先级,进行eval()计算
while(op.size() && pr[op.top()] >= pr[c]) eval();
op.push(c);
}
}
while(op.size()) eval();
return num.top();
}
};
此代码有一个很好的点,那就是模块化,当一个计算多次出现时,将其写入和一个函数,进行函数调用即可。同时,对于优先级的处理,其直接用比较即可,比我的代码要好很多pr[op.top()] >= pr[c]。
97. 交错字符串(动态规划)
题目描述
给定三个字符串 s1
、s2
、s3
,请你帮忙验证 s3
是否是由 s1
和 s2
**交错 组成的。
两个字符串 s
和 t
交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
- 交错 是
s1 + t1 + s2 + t2 + s3 + t3 + ...
或者t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b
意味着字符串 a
和 b
连接。
示例 1:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出:true
示例 2:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false
示例 3:
输入:s1 = "", s2 = "", s3 = ""
输出:true
提示:
0 <= s1.length, s2.length <= 100
0 <= s3.length <= 200
s1
、s2
、和s3
都由小写英文字母组成
我的代码
代码1:首先我想到了双指针,即依次匹配s1、s2,但是这忽略了一种情况,当s1等于aabc,s2等于abb,s3等于aabbabc时,正确结果为True但结果返回false。因此这种方法应该pass掉
代码2:本题需要用到动态规划的思想:
#include <iostream>
#include <vector>
using namespace std;
bool isInterleave(string s1, string s2, string s3) {
int n1 = s1.length(), n2 = s2.length(),n3 = s3.length();
if(n1+n2 != n3) return false;
vector< vector <int> > dp(n1+1,vector<int>(n2+1,0));
dp[0][0] = 1;
for(int i =0; i <= n1; i++){
for(int j = 0; j <= n2; j++){
int p = i+j-1;
if(i > 0){
dp[i][j] |= (s1[i-1] == s3[p] && dp[i-1][j]);
}// |运算是因为上下两种情况只要一种情况为true,那么最终结果就为True
if(j > 0){
dp[i][j] |= (s2[j-1] == s3[p] && dp[i][j-1]);
}
}
}
if(dp[n1][n2] == 1) return true;
else return false;
}
int main(){
string s1,s2,s3;
cin >> s1 >> s2 >> s3;
cout << isInterleave(s1,s2,s3);
return 0;
}
剑指 Offer 46. 把数字翻译成字符串(动态规划)
题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出:5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
我的代码
要不是有提示动态规划,我可能想不到要用动态规划。动态规划思路如下:
- 设dp[i]代表从0开始到下标为i的字符串能够翻译的种数
- dp[i] 与dp[i-1]和dp[i-2]有关,首先num对应的第i位一定可以翻译为某个字母,因此dp[i] = dp[i-1]+1,其次,如果num第i位和第i-1位组成的数字小于26且大于9时,又可以得到另一种翻译,此时dp[i] += dp[i-2]。为什么要大于9呢?因为如果num = 1002,那么对于02来说,其不能翻译成一个字母,只能翻译成两个字母。
代码如下:
class Solution {
public:
vector<int> itostr(int num){
vector<int> ans;
while(num){
ans.push_back(num%10);
num /= 10;
}
reverse(ans.begin(),ans.end());
return ans;
}
int translateNum(int num) {
if(num == 0) return 1;
vector<int> nums = itostr(num);
int n = nums.size();
vector<int> dp(n,0);
dp[0] = 1;
for(int i = 1; i < n; i++){
if(nums[i-1]*10 + nums[i] <= 25 && nums[i-1]*10 + nums[i] >= 10){
if(i>1) dp[i] += dp[i-2];
else dp[i]++;
}
dp[i] += dp[i-1];
}
return dp[n-1];
}
};
32. 最长有效括号(动态规划)
题目描述
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104
s[i]
为'('
或')'
我的代码
其实,最开始我的思路是对的,我第一反应就是用动态规划,但是由于我没有考虑全面,因此,动态规划做出来是错误的,此外,我也没有真正理解题目的意思。我以为只求连续的()括号,然而我并没有考虑到求(())括号嵌套的情况。
法一:动态规划
这是一个 最值型 动态规划的题目。
动态规划题目分析的 4 个步骤:
- 确定状态
研究最优策略的最后一步 - 化为子问题
转移方程
根据子问题定义得到 - 初始条件和边界情况
计算顺序
基于上图分析即可得出动态规划
具体实现 首先,**我们定义一个 dp 数组,其中第 i 个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。**当i为 ')'时分析对于位置括号情况即可。
具体代码如下:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if(n == 0) return 0;
vector<int> dp(n+1,0);
//memset(dp,0,sizeof(dp));
int max_ = 0;
for(int i = 1; i < n; i++){
if(s[i] == ')'){
if(s[i-1] == '('){
dp[i] = i>1? (dp[i-2] + 2) : 2;
}
else if(s[i-1] == ')'){
if(i-dp[i-1]-1 >= 0 && s[i-dp[i-1]-1] == '('){
dp[i] = i-dp[i-1]-2 >= 0? dp[i-1] + 2 + dp[i-dp[i-1]-2] : dp[i-1] + 2;
}
}
cout << "dp[" << i << "]: " << dp[i] << endl;
}
max_ = max(max_,dp[i]);
}
return max_;
}
};
法二:栈
474. 一和零(动态规划)
题目描述
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的 子集 。
示例 1:
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
提示:
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i]
仅由'0'
和'1'
组成1 <= m, n <= 100
我的代码
思路:本题是一道动态规划问题,不过是01背包问题的升级版
class Solution {
public:
void get_num(string str, int &n1, int &n2){ // 获取0和1的数量
for(int i = 0; i < str.length(); i++){
if(str[i] == '1') n2++;
else n1++;
}
}
int findMaxForm(vector<string>& strs, int m, int n) {
int dp[strs.size()+1][m+1][n+1];
memset(dp,0,sizeof(dp));
// dp[l][i][j] 表示长度为l的字符串数组,0的数量最多为i,1的数量最多为j时,最长的子集长度
for(int l = 1; l <= strs.size(); l++){
for(int i = 0; i <= m; i++){
for(int j = 0; j <= n; j++){
int n0 = 0,n1= 0;
get_num(strs[l-1],n0,n1);
if(n0 > i || n1 > j) dp[l][i][j] = dp[l-1][i][j]; // 当第l个字符串不能加入时
else if(i >= n0 && j >= n1){ // 第1个字符串可以加入时
dp[l][i][j] = max(dp[l-1][i-n0][j-n1]+1,dp[l-1][i][j]);
}
}
}
}
return dp[strs.size()][m][n];
}
};
1626. 无矛盾的最佳球队(动态规划)
题目描述
假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。
然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 没有矛盾 的球队。如果一名年龄较小球员的分数 严格大于 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。
给你两个列表 scores
和 ages
,其中每组 scores[i]
和 ages[i]
表示第 i
名球员的分数和年龄。请你返回 所有可能的无矛盾球队中得分最高那支的分数 。
示例 1:
输入:scores = [1,3,5,10,15], ages = [1,2,3,4,5]
输出:34
解释:你可以选中所有球员。
示例 2:
输入:scores = [4,5,6,5], ages = [2,1,2,1]
输出:16
解释:最佳的选择是后 3 名球员。注意,你可以选中多个同龄球员。
示例 3:
输入:scores = [1,2,3,5], ages = [8,9,10,1]
输出:6
解释:最佳的选择是前 3 名球员。
提示:
1 <= scores.length, ages.length <= 1000
scores.length == ages.length
1 <= scores[i] <= 106
1 <= ages[i] <= 1000
我的代码
我的代码思路很简单,就是用动态规划,dp[i]表示从0到i这几个球员得分最高值
- 首先要对这几个球员进行排序,把年龄小的放前边,年龄相同,则将成绩小的放前边。由于已经将年龄从小到大排好序了,因此,在计算时,可以不用考虑主要年龄了。
- dp[i] 有以下几种情况:
- 第i个同学不与前面的队员组成球队,自成一队
- 第i个队员与前边的某些人组成球队
- dp[i] 需要取这些值里边的最大值。
代码如下:
class Solution {
public:
struct people{
int score;
int age;
};
static bool cmp(struct people A, struct people B){
return A.age == B.age? A.score < B.score: A.age < B.age;
}
int bestTeamScore(vector<int>& scores, vector<int>& ages) {
int n = ages.size(),ans = 0;
int dp[n];
memset(dp,0,sizeof(dp));
vector<struct people> peo(n);
for(int i = 0; i < n; i++){
peo[i].score = scores[i];
peo[i].age = ages[i];
}
// 排序
sort(peo.begin(),peo.end(),cmp);
for(int i = 0; i < n; i++){
// 初始化dp[i] ,也即每个人都自成一队的情况
dp[i] = peo[i].score;
// 每次更新dp[i],都要更新最大值ans
ans = max(ans,dp[i]);
}
for(int i = 1; i < n; i++){
for(int j = i-1; j >= 0; j--){
// 遍历第i个队员前的所有人,找出最大值存入dp[i]
if(peo[j].score <= peo[i].score){
dp[i] = max(dp[i],dp[j] + peo[i].score);
}
}
ans = max(ans,dp[i]);
}
return ans;
}
};
139. 单词拆分(动态规划)
题目描述
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为"applepenapple" 可以由"apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅有小写英文字母组成wordDict
中的所有字符串 互不相同
我的代码
我首先想到的是深度优先,通过遍历所有的可能性,然后遇到True则返回结果。但是可想而知,当字典数目太大时,时间复杂度会特别高。因此没有AC
错误代码如下:
class Solution {
public:
bool ans = false;
void dfs(string s, vector<string> wordDict, bool& flag,int len){
if(len <= 0){
ans = true;
flag = true;
return ;
}
else{
for(int i = 0; i < wordDict.size() && !flag; i++){
if(!flag){
int x= s.find(wordDict[i]);
if(x != -1){
string ss = s.substr(0,x);
for(int j = 0; j < wordDict[i].size(); j++) ss+= " ";
ss += s.substr(x+wordDict[i].length(),s.length()-x-wordDict[i].length());
cout << ss << endl;
dfs(ss,wordDict,flag,len-wordDict[i].length());
}
}
}
}
}
bool wordBreak(string s, vector<string>& wordDict) {
bool flag = false;
dfs(s,wordDict,flag,s.length());
return ans;
}
};
而且,我本来的方法也不对,因为我的想法是,如果遇到匹配的单词,则简单的将单词从string中删除,进行下一步递归,但是我没有想到删掉之后会改变string的构造,因此不能简单删除,而是用“ ”空格来代替被匹配的子串。
代码2
本题主要考察动态规划,dp[i]代表字符串s的前i个字符可以由字典表示出来
dp[i]与前边的dp数组的关系如下:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.length();
vector<bool> dp(n + 1,false); // dp[i] 代表前i个字符可以由字典组成的布尔值
dp[0] = true; //前零个字符可以
for(int i = 1; i<= s.length(); i++){
cout << "i:" << i << endl;
for(int j = 0; j < i; j++){
cout << s.substr(j,i-j) << endl;
// 这里取子串要注意,i和j代表的都是字母的个数而非数组下标
if(find(wordDict.begin(),wordDict.end(),s.substr(j,i-j)) != wordDict.end() && dp[j] == true){
cout << "true" << endl;
dp[i] = true;
break;
}
}
cout << endl;
}
return dp[s.length()];
}
};
53. 最大子数组和(动态规划)
题目描述
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
104 <= nums[i] <= 104
**进阶:**如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
我的代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int dp[n+5];
dp[0] = nums[0];
// dp[i]表示以第i个元素为尾部的连续子数组的最大和
// dp[i] 有两种情况,
// 1. nums[i]自成一个子数组
// 2. nums[i]和前i个中的部分成为子数组
// dp[i] 要取得最大值。
for(int i = 1; i < n; i++){
dp[i] = max(dp[i-1] + nums[i],nums[i]);
}
return *max_element(dp,dp+n);
}
};
剑指 Offer II 095. 最长公共子序列(动态规划)
题目描述
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 **是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1
和text2
仅由小写英文字符组成。
我的代码
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
// 深度优先不好做,用动态规划比较好
// dp[i][j] 表示,text1[0...i-1]text2[0...j-1] 这两个子串的最长公共子序列
// 如果text1[i-1] == text2[j-1] 则dp[i][j] = dp[i-1][j-1] +1 ;; 注意动态规划中的i与数组中的i-1相对应
// 如果不等于,那么dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
// 初始条件是:dp[0][0...n] = dp[0...n][0] = 0; //因为一个子串长度为0,那肯定没有公共子序列呀!
vector<vector<int>> dp(text1.length()+1,vector<int>(text2.length()+1,0));
for(int i = 1; i <= text1.length(); i++){
for(int j = 1; j <= text2.length(); j++){
if(text1[i-1] == text2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}
else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
};
KY213 奇偶校验
题目描述
输入一个字符串,然后对每个字符进行奇校验,最后输出校验后的二进制数(如’3’,输出:10110011)。
输入描述:
输入包括一个字符串,字符串长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 对于字符串中的每一个字符,输出按题目进行奇偶校验后的数,每个字符校验的结果占一行。
我的代码
**代码思路:**信息是以比特流的方式传输的,类似01000001。在传输过程中,有可能会发生错误,比如,我们存储了01000001,但是取出来却是01000000,即低位由0变成了1。为了检测到这种错误,我们可以通过“奇偶校验”来实现。假如,我们存储的数据是一个字节,8个比特位,那我们就可以计算每个字节比特位是1的个数,如果是偶数个1,那么,我们就把第九个位设为1,如果是奇数个1,那么就把第九个位设为0,这样连续9个字节比特位为1的位数肯定是奇数。这中方法叫做“奇校验”,“偶校验”和此类似。当然,在实际应用中,也可以把一个字节的前7位作为数据位,最后一个为作为校验位。
比如说对字符‘3’进行奇偶校验。'3’的ascii值为51,51对应二进制为 0110011(用七位表示) 其中1的个数为4(偶数)个。所以在最高为添1 所以’3’的奇校验为10110011。bitset
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main(){
int x;
char a;
while(cin >> a){
x = 0;
int count = 0;
x += a;
while(a){
count += a%2;
a = a/2;
}
if((count % 2) == 0){ //奇数个1
x += 128; // 第七位置为1
}
cout << bitset<8>(x) << '\n';
}
return 0;
}
KY216 遍历链表
题目描述
建立一个升序链表并遍历输出。
输入描述:
输入的每个案例中第一行包括1个整数:n(1<=n<=1000),接下来的一行包括n个整数。
输出描述:
可能有多组测试数据,对于每组数据, 将n个整数建立升序链表,之后遍历链表并输出。
我的代码
方法一:在输入数据时就开始构造一个升序列表,用insert()递归构造;
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
struct Linknode{
int value;
Linknode *next;
Linknode(int value1, Linknode *next1 = nullptr){
value = value1;
next = next1;
}
};
void insert(int x,Linknode *&P){
if(P == nullptr){
P = new Linknode(x);
}
else if(P->value <= x){
insert(x,P->next);
}
else{
int t = P->value;
P->value = x;
insert(t,P->next);
}
}
int main (){
Linknode *numberList = nullptr;
int n;
cin >> n;
for(int i = 0; i < n; i++){
int x;
cin >> x;
insert(x,numberList);
}
for(int i = 0; i < n; i++){
cout << numberList->value << ' ';
numberList = numberList->next;
}
}
法二:利用list的sort函数排序
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
struct Linknode{
int value;
Linknode *next;
Linknode(int value1, Linknode *next1 = nullptr){
value = value1;
next = next1;
}
};
int main (){
Linknode *numberList = nullptr;
list<int> LL;
int n;
cin >> n;
for(int i = 0; i < n; i++){
int x;
cin >> x;
LL.push_back(x);
}
LL.sort();
for(int i = 0; i < n; i++){
cout << LL.front() << ' ';
LL.pop_front();
}
}
[list类KY216 遍历链表 ](https://www.notion.so/list-62e6adab5cb04d61aa540814cceebfe4?pvs=21)
36. 有效的数独
166. 分数到小数(竖式除法)
题目描述
给定两个整数,分别表示分数的分子 numerator
和分母 denominator
,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
如果存在多个答案,只需返回 任意一个 。
对于所有给定的输入,保证 答案字符串的长度小于 104
。
示例 1:
输入:numerator = 1, denominator = 2
输出:"0.5"
示例 2:
输入:numerator = 2, denominator = 1
输出:"2"
示例 3:
输入:numerator = 4, denominator = 333
输出:"0.(012)"
提示:
231 <= numerator, denominator <= 231 - 1
denominator != 0
我的代码
代码思路:本题主要是考察模拟除法以及哈希表的运用,模拟除法如下图所示。
长除法
题目要求根据给定的分子和分母,将分数转成整数或小数。由于给定的分子和分母的取值范围,为了防止计算过程中产生溢出,需要将分子和分母转成 64 位整数表示。
将分数转成整数或小数,做法是计算分子和分母相除的结果。可能的结果有三种:整数、有限小数、无限循环小数。
如果分子可以被分母整除,则结果是整数,将分子除以分母的商以字符串的形式返回即可。
如果分子不能被分母整除,则结果是有限小数或无限循环小数,需要通过模拟长除法的方式计算结果。为了方便处理,首先根据分子和分母的正负决定结果的正负(注意此时分子和分母都不为 0),然后将分子和分母都转成正数,再计算长除法。
计算长除法时,首先计算结果的整数部分,将以下部分依次拼接到结果中:
如果结果是负数则将负号拼接到结果中,如果结果是正数则跳过这一步;
将整数部分拼接到结果中;
将小数点拼接到结果中。
完成上述拼接之后,根据余数计算小数部分。
计算小数部分时,每次将余数乘以 10,然后计算小数的下一位数字,并得到新的余数。重复上述操作直到余数变成 0 或者找到循环节。
如果余数变成 0,则结果是有限小数,将小数部分拼接到结果中。
如果找到循环节,则找到循环节的开始位置和结束位置并加上括号,然后将小数部分拼接到结果中。
如何判断是否找到循环节?注意到对于相同的余数,计算得到的小数的下一位数字一定是相同的,因此如果计算过程中发现某一位的余数在之前已经出现过,则为找到循环节。为了记录每个余数是否已经出现过,需要使用哈希表存储每个余数在小数部分第一次出现的下标。
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
// 为了避免在余数*10的过程中int溢出
long longnumerator = numerator, longdenominator = denominator;
// 当在求余数的过程中,如果遇到了相同余数,则说明出现了循环小数,所以此处用哈希表存储余数和对应的index值
// 存储index值是为了好构造(循环小数)字符串
unordered_map<long,int> yushumap;
string ans,shang,xiaoshu;
// 先判断两个数是否异号,若是则在结果中加入‘-’,然后取绝对值
if((numerator > 0 && denominator < 0) || (numerator < 0 && denominator > 0) )
ans.push_back('-');
// 取绝对值,ans()的头文件位<math.h>
longnumerator = abs(longnumerator);
longdenominator = abs(longdenominator);
// 如果可以直接整除,那么直接用to_string()函数将商转为string,注意此处要添加符号
if(longnumerator % longdenominator == 0) return ans + to_string(longnumerator / longdenominator);// 可以整除,则利用to_string()直接将商转换位string
// 若不能整除,则先将商转为string
shang = to_string(longnumerator/longdenominator);
// 添加小数点
shang.push_back('.');
// 模拟除法,每次余数*10 再继续除
long yushu = longnumerator % longdenominator;
yushu *= 10;
int index = 0; // 记录小数在xiaoshu字符串中的位置
// 处理小数,当余数为零或者找到循环点则退出循环
while(yushu != 0 && !yushumap.count(yushu)){
xiaoshu.push_back(yushu/longdenominator + '0');
yushumap[yushu] = index++;
// 提取新一轮余数
yushu = yushu % longdenominator;
yushu *= 10;
}
// 找到循环点,则处理成(循环小数)的形式
if(yushumap.count(yushu)){
string temp = xiaoshu.substr(0,yushumap[yushu]) + "(" + xiaoshu.substr(yushumap[yushu],index);
temp += ")";
xiaoshu = temp;
}
// 注意此处的 += 是为了添加符号
ans += shang + xiaoshu;
return ans;
}
};
179. 最大数
题目描述
给定一组非负整数 nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
**注意:**输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例 1:
输入:nums = [10,2]输出:"210"
示例 2:
输入:nums = [3,30,34,5,9]输出:"9534330"
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 109
我的代码
首先,我想到了对每个字符进行排序,主要就是要编写sort函数中的参数,cmp函数,但是判断过程比较复杂,花了很长的时间都没能完全AC,看了题解,有一个十分便捷的方法,那就是将两个string分别排序然后直接比较大小。代码如下:
我真是个憨憨
class Solution {
public:
static bool cmp1(string A,string B){
string temp1 = A + B;
string temp2 = B + A;
return temp1 > temp2;
}
string largestNumber(vector<int>& nums) {
string ans;
vector<string> num;
for(int i = 0; i < nums.size(); i++){
num.push_back(to_string(nums[i]));
}
sort(num.begin(),num.end(), cmp1);
if(num[0] == "0") return "0";
for(int i = 0; i < nums.size(); i++){
ans += num[i];
cout << num[i] << endl;
}
return ans;
}
};
331. 验证二叉树的前序序列化(槽位)
题目描述
序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #
。
例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#"
,其中 #
代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
保证 每个以逗号分隔的字符或为一个整数或为一个表示 null
指针的 '#'
。
你可以认为输入格式总是有效的
- 例如它永远不会包含两个连续的逗号,比如
"1,,3"
。
**注意:**不允许重建树。
示例 1:
输入:preorder ="9,3,4,#,#,1,#,#,2,#,6,#,#"输出:true
示例 2:
输入:preorder ="1,#"输出:false
示例 3:
输入:preorder ="9,#,#,1"输出:false
提示:
1 <= preorder.length <= 104
preorder
由以逗号“,”
分隔的[0,100]
范围内的整数和“#”
组成
我的代码
这道题的解题思路很特别:
运用栈:
我们可以定义一个概念,叫做槽位。一个槽位可以被看作「当前二叉树中正在等待被节点填充」的那些位置。
二叉树的建立也伴随着槽位数量的变化。每当遇到一个节点时:
如果遇到了空节点,则要消耗一个槽位;
如果遇到了非空节点,则除了消耗一个槽位外,还要再补充两个槽位。
此外,还需要将根节点作为特殊情况处理。
我们使用栈来维护槽位的变化。栈中的每个元素,代表了对应节点处剩余槽位的数量,而栈顶元素就对应着下一步可用的槽位数量。当遇到空节点时,仅将栈顶元素减 111;当遇到非空节点时,将栈顶元素减 111 后,再向栈中压入一个 222。无论何时,如果栈顶元素变为 000,就立刻将栈顶弹出。
遍历结束后,若栈为空,说明没有待填充的槽位,因此是一个合法序列;否则若栈不为空,则序列不合法。此外,在遍历的过程中,若槽位数量不足,则序列不合法。
class Solution {
public:
bool isValidSerialization(string preorder) {
int n = preorder.length();
stack<int> pos; //维护槽位数,为什么要用栈呢?为了方便随时记录,其实也可以直接用一个int变量来记录
pos.push(1); // 代表栈顶需要一个槽位
int i = 0;
while(i < n && !pos.empty()){
if(preorder[i] == ',') {i++;continue;} // 跳过,
if(preorder[i] == '#'){
pos.top()--; // 消耗一个槽位
if(pos.top() <= 0) pos.pop(); // 如果栈顶槽位为0,则出栈
i++;
continue;
}
while(isdigit(preorder[i])){ // 跳过数字,其实不关注数字具体是多少
i++;
}
pos.top()--;
if(pos.top() <= 0) pos.pop();
pos.push(2);
}
if(pos.empty() && i >= n) return true; // 当树遍历完了,然后槽位位置也刚好为0时,返回true
else return false;
}
};
11. 盛最多水的容器(双指针)
题目描述
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
我的代码
题解:
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int l = 0,r = n-1; // 双指针
int max = (r-l) * min(height[l],height[r]);
while(l < r){
if(height[l] <= height[r]){
l++;
}
else r--;
if(l < r) max = max < ((r-l) * min(height[l],height[r])) ? (r-l) * min(height[l],height[r]) : max;
}
return max;
}
};
640. 求解方程(模拟)
题目描述
求解一个给定的方程,将x
以字符串 "x=#value"
的形式返回。该方程仅包含 '+'
, '-'
操作,变量 x
和其对应系数。
如果方程没有解或存在的解不为整数,请返回 "No solution"
。如果方程有无限解,则返回 “Infinite solutions”
。
题目保证,如果方程中只有一个解,则 ‘x’ 的值是一个整数。
示例 1:
输入: equation = "x+5-3+x=6+x-2"
输出: "x=2"
示例 2:
输入: equation = "x=x"
输出: "Infinite solutions"
示例 3:
输入: equation = "2x=x"
输出: "x=0"
提示:
3 <= equation.length <= 1000
equation
只有一个'='
.- 方程由绝对值在
[0, 100]
范围内且无任何前导零的整数和变量'x'
组成。
我的代码
代码思路其实很简单,就是统计变量x的系数,以及常数值。其次就是什么情况下为No Solution,什么情况下为Infinite solutions
最开始我的思路为,用numl和numr分别统计‘=‘左右两边的整数,Xl和Xr来统计’=‘左右两边的X变量数。但是我思考得还不够全面。没有考虑到以下几种情况:
- 0x,也即x前系数为0得情况
- -2x,x前系数为负数得情况
- -x,-x这一特殊变量
此外,我也没有想清楚什么情况下为No Solution,什么情况下为Infinite solutions:
- 当左右两边的x变量系数相等时,且左右两边常数相等时,为Infinite solutions
- 当左右两边系数相等且左右两边常数不等时,为No Solution
- 其余情况则可以直接相除,得到正确结果。
由于以上原因,我编写代码得过程并不顺利,Bug层出。此处可参考官方思路:
官方代码如下:
class Solution {
public:
string solveEquation(string equation) {
int factor = 0, val = 0;
int index = 0, n = equation.size(), sign1 = 1; // 等式左边默认系数为正
while (index < n) {
if (equation[index] == '=') {
sign1 = -1; // 等式右边默认系数为负
index++;
continue;
}
int sign2 = sign1, number = 0;
bool valid = false; // 记录 number 是否有效
if (equation[index] == '-' || equation[index] == '+') { // 去掉前面的符号
sign2 = (equation[index] == '-') ? -sign1 : sign1;
index++;
}
while (index < n && isdigit(equation[index])) {
number = number * 10 + (equation[index] - '0');
index++;
valid = true;
}
if (index < n && equation[index] == 'x') { // 变量
factor += valid ? sign2 * number : sign2;
index++;
} else { // 数值
val += sign2 * number;
}
}
if (factor == 0) {
return val == 0 ? "Infinite solutions" : "No solution";
}
return string("x=") + to_string(-val / factor);
}
};
注:像这种,从字符串中提取数字的题目,并且涉及到±号的时候,建议用while语句一次性提取出数字,然后向后遍历,如果用for循环,可能不太直观。
31. 下一个排列
题目描述
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3]
,以下这些都可以视作arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]
的下一个排列是[1,3,2]
。 - 类似地,
arr = [2,3,1]
的下一个排列是[3,1,2]
。 - 而
arr = [3,2,1]
的下一个排列是[1,2,3]
,因为[3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
我的代码
代码思路很简单,从数组末尾开始找,找到第一个降序的数据对i-1,i
,然后从末尾升序队列中找到第一个大于第i-1
个数的值j
,交换j与i-1
,然后将末尾剩余的数反转即可。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
if(n == 1){
cout << nums[0] << endl;
return ;
}
int i = n-1,j;
while(i > 0){
if(nums[i] > nums[i-1]) break;
i--;
}
if(i <= 0) reverse(nums.begin(),nums.end());
else{
int key = nums[i-1];
for(j = n-1; j > i-1; j--){
if(nums[j] > key) break;
}
swap(nums[i-1],nums[j]);
// int m = n-1-i+1;
// for(int h = 0; h < m-1; h++){
// for(int x = i; x < n-h-1; x++){
// if(nums[x] > nums[x+1]) swap(nums[x],nums[x+1]);
// }
// }
reverse(nums.begin()+i,nums.end());
}
cout << "[" << nums[0];
for(int h = 1; h < n; h++){
cout << "," << nums[h];
}
cout << "]" << endl;
}
};
最开始我使用的是冒泡排序,但是后来发现,直接就地翻转即可。
200. 岛屿数量(DFS、BFS)
题目描述
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
的值为'0'
或'1'
我的代码
一开始我想到的是动态规划,但是,想半天没有把子问题想清楚,因此,本题不能用动态规划。
思路如下:
思路一:深度优先遍历 DFS
目标是找到矩阵中 “岛屿的数量” ,上下左右相连的 1 都被认为是连续岛屿。
dfs方法: 设目前指针指向一个岛屿中的某一点 (i, j),寻找包括此点的岛屿边界。
从 (i, j) 向此点的上下左右 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 做深度搜索。
终止条件:
- (i, j) 越过矩阵边界;
- grid[i][j] == 0,代表此分支已越过岛屿边界。
搜索岛屿的同时,执行 grid[i][j] = ‘0’,即将岛屿所有节点删除,以免之后重复搜索相同岛屿。
主循环:
遍历整个矩阵,当遇到 grid[i][j] == ‘1’ 时,从此点开始做深度优先搜索 dfs,岛屿数 count + 1 且在深度优先搜索中删除此岛屿。
最终返回岛屿数 count 即可。
思路二:广度优先遍历 BFS
主循环和思路一类似,不同点是在于搜索某岛屿边界的方法不同。
bfs 方法:
借用一个队列 queue,判断队列首部节点 (i, j) 是否未越界且为 1:
若是则置零(删除岛屿节点),并将此节点上下左右节点 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 加入队列;
若不是则跳过此节点;
循环 pop 队列首节点,直到整个队列为空,此时已经遍历完此岛屿。
class Solution {
public:
int m,n;
void dfs(vector<vector<char>>& grid, int i, int j){
if(i < 0 || i >= m || j < 0 || j >= n) return;
if(grid[i][j] == '1'){
grid[i][j] = '0';
dfs(grid,i-1,j);
dfs(grid,i,j-1);
dfs(grid,i+1,j);
dfs(grid,i,j+1);
}
}
void bfs(vector<vector<char>>& grid, int i, int j){
queue<pair<int,int>> que;
if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return;
que.push(pair<int,int>(i,j));
while(!que.empty()){
pair<int,int> pos = que.front();
que.pop();
int ii = pos.first, jj = pos.second;
if(ii < 0 || ii >= m || jj < 0 || jj >= n || grid[ii][jj] == '0') continue;
grid[ii][jj] = '0';
que.push(pair<int,int>(ii,jj-1));
que.push(pair<int,int>(ii-1,jj));
que.push(pair<int,int>(ii,jj+1));
que.push(pair<int,int>(ii+1,jj));
}
}
int numIslands(vector<vector<char>>& grid) {
m = grid.size();
n = grid[0].size();
int ans = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(grid[i][j] == '1'){
ans++;
grid[i][j] = '0';
// dfs(grid,i-1,j);
// dfs(grid,i,j-1);
// dfs(grid,i+1,j);
// dfs(grid,i,j+1);
bfs(grid,i-1,j);
bfs(grid,i,j-1);
bfs(grid,i+1,j);
bfs(grid,i,j+1);
}
}
}
return ans;
}
};
面试题13. 机器人的运动范围(DFS)
题目描述
地上有一个m行n列的方格,从坐标 [0,0]
到坐标 [m-1,n-1]
。一个机器人从坐标 [0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
我的代码
可达解分析:
根据数位和增量公式得知,数位和每逢 进位 突变一次。根据此特点,矩阵中 满足数位和的解 构成的几何形状形如多个 等腰直角三角形 ,每个三角形的直角顶点位于 0,10,20,…0, 10, 20, …0,10,20,… 等数位和突变的矩阵索引处 。
三角形内的解虽然都满足数位和要求,但由于机器人每步只能走一个单元格,而三角形间不一定是连通的,因此机器人不一定能到达,称之为 不可达解 ;同理,可到达的解称为 可达解 (本题求此解) 。
图例展示了 n,m=20n,m = 20n,m=20 , k∈[6,19]k \in [6, 19]k∈[6,19] 的可达解、不可达解、非解,以及连通性的变化。
根据可达解的结构和连通性,易推出机器人可 仅通过向右和向下移动,访问所有可达解 。
三角形内部: 全部连通,易证;
两三角形连通处: 若某三角形内的解为可达解,则必与其左边或上边的三角形连通(即相交),即机器人必可从左边或上边走进此三角形。
class Solution {
public:
bool valid(int k, int x, int y){
int num = 0;
while(x){
num += (x%10);
x /= 10;
}
while(y){
num += (y%10);
y /= 10;
}
if(num > k) return false;
return true;
}
int dfs(int m, int n, int i, int j, int k,vector<vector<int>> &visit){
if(i < 0 || i >= m || j < 0|| j >= n || !valid(k,i,j) || visit[i][j]) return 0;
cout << i << " " << j << endl;
visit[i][j] = 1; // 这个其实就是在记录不可达解
if(i == m-1 && j == n-1){ // 可达解
return 1;
}
return 1 + dfs(m,n,i+1,j,k,visit) + dfs(m,n,i-1,j,k,visit) + dfs(m,n,i,j+1,k,visit) + dfs(m,n,i,j-1,k,visit);
}
int movingCount(int m, int n, int k) {
vector<vector<int>> visit(m,vector<int>(n,0));
return dfs(m,n,0,0,k,visit);
}
};
297. 二叉树的序列化与反序列化
题目描述
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例 1:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
提示:
- 树中结点数在范围
[0, 104]
内 1000 <= Node.val <= 1000
题解(本题思路不难,但是我没过,超时了,此处贴题解)
45. 跳跃游戏 II(贪心)
题目描述
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是2。
从下标为 0 跳到下标为 1 的位置,跳1 步,然后跳3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
- 题目保证可以到达
nums[n-1]
我的代码
方法二:正向查找可到达的最大位置
方法一虽然直观,但是时间复杂度比较高,有没有办法降低时间复杂度呢?
如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
例如,对于数组 [2,3,1,2,4,2,3],初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。
从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。
在具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1。
在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
class Solution {
public:
int jump(vector<int>& nums) {
int ans = 0;
int i = 0,max_pos=0,max_index = 0;
// max_pos代表当走到max_index时,最远可以走的位置,它可以大于等于nums的长度
// 因为只有max_pos大于nums的长度时,才可能走到结尾。
if(nums[0] == 0) return ans;
if(nums.size() == 1) return 0; // 当只有一个节点时,根本不需要跳即可到达终点
while(i < nums.size()-1){
// 为什么要去掉最后一个节点呢?因为如果可以走到最后一个节点,那么就会使得ans多计算一次
for(int j = 1; j <= nums[i]; j++){
if(i + j >= nums.size()-1) // 已经可以到达终点了,那么直接返回结果
return ans+1;
if(i + j < nums.size()-1){
if(i+j+nums[i+j] > max_pos){
max_pos = i+j+nums[i+j];
max_index = j+i;
}
}
}
cout << max_pos << endl;
cout << max_index << endl;
ans++;
i = max_index;
}
return ans;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置(二分查找)
题目描述
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 105
109 <= nums[i] <= 109
nums
是一个非递减数组109 <= target <= 109
我的代码
本题是一道很典型的二分查找问题,因为所给的数据保证了非递减序列。
代码1
因此就有了第一段代码,由于最开始并不了解我自己背的二分查找的模板查找出来的位置有什么特点,因此,起开始的代码思路如下:
- 通过Binarysearch查找到目标target的位置,若找到,则从该点出发,分别往左和往右找到边界
class Solution {
public:
int Binarysearch(vector<int> nums, int key){
int l = 0,r = nums.size()-1,mid;
while(l < r){
mid = (l+r) /2;
if(key > nums[mid]) l = mid+1;
else r = mid;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(nums.empty()){
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
int pos = Binarysearch(nums,target),left = pos-1,right = pos+1;
cout << pos << endl;
if(nums[pos] != target){ // 未找到
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
while(left >= 0 && nums[left] == target){
left--;
} // 找到左边界-1
left++;
while(right < nums.size() && nums[right] == target){
right++;
}// 右边界+1
right--;
ans.push_back(left);
ans.push_back(right);
return ans;
}
};
代码2
但是当我了解了该模板的特性后,就有了以下修改二分查找
- 可以通过Binarysearch直接查找到边界
- 左边界正常查找,右边界通过查找target+1来确定
class Solution {
public:
int Binarysearch(vector<int> nums, int key){
int l = 0,r = nums.size()-1,mid;
while(l < r){
mid = (l+r) /2;
if(key > nums[mid]) l = mid+1;
else r = mid;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(nums.empty()){
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
int left = Binarysearch(nums,target);
int right = Binarysearch(nums,target+1)-1;
if(nums[left] != target) {
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
if(right + 1 < nums.size() && nums[right+1] == target) right++;
ans.push_back(left);
ans.push_back(right);
return ans;
}
};
187. 重复的DNA序列(哈希、滑动窗口、位运算)
题目描述
DNA序列 由一系列核苷酸组成,缩写为 'A'
, 'C'
, 'G'
和 'T'
.。
- 例如,
"ACGAATTCCG"
是一个 DNA序列 。
在研究 DNA 时,识别 DNA 中的重复序列非常有用。
给定一个表示 DNA序列 的字符串 s
,返回所有在 DNA 分子中出现不止一次的 长度为 10
的序列(子字符串)。你可以按 任意顺序 返回答案。
示例 1:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC","CCCCCAAAAA"]
示例 2:
输入:s = "AAAAAAAAAAAAA"
输出:["AAAAAAAAAA"]
提示:
0 <= s.length <= 105
s[i]=='A'
、'C'
、'G'
or'T'
我的代码
代码1
其实这道题很简单,直接从0开始到n-10,把每个子串遍历一遍,然后用哈希表来存储每种字串出现的频度。统计频度大于1的子串。
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
int n = s.length();
vector<string> ans;
if(n <= 10) return ans;
map<string,int> strMap;
for(int i = 0; i <= n-10; i++){
string temp = s.substr(i,10);
if(strMap.count(temp) > 0){ // 找到
cout << temp << "++";
strMap[temp]++;
if(strMap[temp] == 2) ans.push_back(temp);
cout << strMap[temp] << endl;
}
else strMap[temp] = 1;
}
// auto it = strMap.begin();
// while(it != strMap.end()){
// if(it->second > 1 && find(ans.begin(),ans.end(),it->first) == ans.end()){
// ans.push_back(it->first);
// }
// it++;
// }
return ans;
}
};
以上代码有一个非常棒的思路:if(strMap[temp] == 2) ans.push_back(temp);
可以看到,这一句话就能替代下边一整个循环
代码2
代码二的思路是滑动窗口+哈希表+位运算,简单来说,就是将每个字母设置一个2位的2进制数,然后将10个2位2进制数组成一个int(只用低20位)然后用滑动窗口的思想来在常数时间内计算出下一个字符串的int值。提高时间复杂度。
207. 课程表(拓扑排序,广度优先BFS)
题目描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
我的代码
代码思路
其实,拿到这道题,没有想到过是拓扑排序,此外,我也没写过拓扑排序。因此还是看了题解,题解如下:
一共有 n 门课要上,编号为 0 ~ n-1。
先决条件 [1, 0],意思是必须先上课 0,才能上课 1。
给你 n、和一个先决条件表,请你判断能否完成所有课程。
再举个生活的例子
先穿内裤再穿裤子,先穿打底再穿外套,先穿衣服再戴帽子,是约定俗成的。
内裤外穿、光着身子戴帽子等,都会有点奇怪。
我们遵循穿衣的一条条先后规则,用一串 顺序行为,把衣服一件件穿上。
我们遵循课程之间的先后规则,找到一种上课顺序,把所有课一节节上完。
用有向图描述依赖关系
示例:n = 6,先决条件表:[[3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4]]
课 0, 1, 2 没有先修课,可以直接选。其余的课,都有两门先修课。
我们用有向图来展现这种依赖关系(做事情的先后关系):
这种叫 有向无环图,把一个 有向无环图 转成 线性的排序 就叫 拓扑排序。
有向图有 入度 和 出度 的概念:
如果存在一条有向边 A --> B,则这条边给 A 增加了 1 个出度,给 B 增加了 1 个入度。
所以,顶点 0、1、2 的入度为 0。顶点 3、4、5 的入度为 2。
每次只能选你能上的课
每次只能选入度为 0 的课,因为它不依赖别的课,是当下你能上的课。
假设选了 0,课 3 的先修课少了一门,入度由 2 变 1。
接着选 1,导致课 3 的入度变 0,课 4 的入度由 2 变 1。
接着选 2,导致课 4 的入度变 0。
现在,课 3 和课 4 的入度为 0。继续选入度为 0 的课……直到选不到入度为 0 的课。
这很像 BFS
让入度为 0 的课入列,它们是能直接选的课。
然后逐个出列,出列代表着课被选,需要减小相关课的入度。
如果相关课的入度新变为 0,安排它入列、再出列……直到没有入度为 0 的课可入列。
BFS 前的准备工作
每门课的入度需要被记录,我们关心入度值的变化。
课程之间的依赖关系也要被记录,我们关心选当前课会减小哪些课的入度。
因此我们需要选择合适的数据结构,去存这些数据:
入度数组:课号 0 到 n - 1 作为索引,通过遍历先决条件表求出对应的初始入度。
邻接表:用哈希表记录依赖关系(也可以用二维矩阵,但有点大)
key:课号
value:依赖这门课的后续课(数组)
怎么判断能否修完所有课?
BFS 结束时,如果仍有课的入度不为 0,无法被选,完成不了所有课。否则,能找到一种顺序把所有课上完。
或者:用一个变量 count 记录入列的顶点个数,最后判断 count 是否等于总课程数。
代码如下
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int,vector<int>> graph(numCourses); //图
vector<int> inDegree(numCourses,0); // 入度数组
for(int i = 0; i < prerequisites.size(); i++){
inDegree[prerequisites[i][0]]++;
// 初始化邻接表
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
queue<int> course;
int num = 0;
for(int i =0; i< numCourses; i++){
// 将入度为零的点进队列
if(inDegree[i] == 0) course.push(i);
}
while(!course.empty()){
int course_number = course.front();
course.pop();
// 选了一门入度为0的课程
num++;
// 获得与他相关的课程(也即邻接点),然后对应的入度-1
vector<int> points = graph[course_number];
for(int i = 0; i < points.size(); i++){
inDegree[points[i]]--;
// 若该课程入度减为零,则可以被选择,进入队列等待被选
if(inDegree[points[i]] == 0) course.push(points[i]);
}
}
if(num == numCourses) return true;
else return false;
}
};
1584. 连接所有点的最小费用(克鲁斯卡尔,并查集)
题目描述
给你一个points
数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]
。
连接点 [xi, yi]
和点 [xj, yj]
的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj|
,其中 |val|
表示 val
的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
示例 1:
输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:
我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。
示例 2:
输入:points = [[3,12],[-2,5],[-4,1]]
输出:18
示例 3:
输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4
示例 4:
输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000
示例 5:
输入:points = [[0,0]]
输出:0
提示:
1 <= points.length <= 1000
106 <= xi, yi <= 106
- 所有点
(xi, yi)
两两不同。
代码思路
这个题过于难了,就掌握以下基本思路就行,考到我认了。移步链接👇
8. 字符串转换整数 (atoi)
题目描述
请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi
函数)。
函数 myAtoi(string s)
的算法如下:
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
。 - 返回整数作为最终结果。
注意:
- 本题中的空白字符只包括空格字符
' '
。 - 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
示例 1:
输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"42"(读入 "42")
^
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。
示例 2:
输入:s = " -42"
输出:-42
解释:
第 1 步:"-42"(读入前导空格,但忽视掉)
^
第 2 步:"-42"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:"-42"(读入 "42")
^
解析得到整数 -42 。
由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。
示例 3:
输入:s = "4193 with words"
输出:4193
解释:
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。
提示:
0 <= s.length <= 200
s
由英文字母(大写和小写)、数字(0-9
)、' '
、'+'
、'-'
和'.'
组成
我的代码
本题思路十分简单,就是处理字符串问题,难点在于如何处理最大INT和最小INT,本题采用了一下思路:
- 首先将要转换的数字num设置为long型——为了避免在进行stringtoint转换时出现溢出问题。
- 然后处理正负号
- 溢出判断——num - 1 ≥ INT_MAX,这里为什么这样写呢?
- 因为sting没有正负号,正负号存在了变量positive中,如果为负数,则最小值为-232,整数最大值为232-1,
- 如果num≥ 2^32且符号为正,则判断正溢出
- 如果num > 2^32 且符号为负,则判断负溢出
class Solution {
public:
int myAtoi(string s) {
int pos = 0,flag = 0;
long ans = 0;
bool positive = true;
if(s[pos] == ' '){
while(pos < s.size() && s[pos] == ' ') pos++;
}
if(s[pos] == '-'){
positive = false;
}
if(s[pos] == '+' || s[pos] == '-') pos++;
while((s[pos] >= '0' && s[pos] <= '9') && (pos < s.size()) ){
flag = 1;
ans = ans*10+s[pos]-'0';
cout << ans << endl;
if(ans-1 >= INT_MAX && !positive) {
return INT_MIN;
}
else if(ans > INT_MAX && positive){
return INT_MAX;
}
pos++;
}
if(!positive) ans *= -1;
return ans;
}
};
3. 无重复字符的最长子串(滑动窗口)
题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入:s = "abcabcbb"
输出:3
解释: 因为无重复字符的最长子串是"abc",所以其长度为 3。
示例 2:
输入:s = "bbbbb"
输出:1
解释:因为无重复字符的最长子串是"b",所以其长度为 1。
示例 3:
输入:s = "pwwkew"
输出:3
解释:因为无重复字符的最长子串是"wke",所以其长度为 3。
请注意,你的答案必须是子串的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
我的代码
本题思路就是滑动窗口,当新加入的字母满足条件时,窗口长度增加,当不满足时,则窗口左边出一个元素,右边进一个元素,继续查看(保持最长长度)。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
string str; // 滑动窗口(动态的)
int ans = 1;
if(s.length() == 0) return 0;
str.push_back(s[0]);
int left = 0,right = left+1;
while(right < s.length()){
if(str.find(s[right]) != -1) // 有重复
{
ans = max(ans,(int)str.length());
str.erase(str.begin(),str.begin()+1);
}
else{
str.push_back(s[right]);
right++;
ans = max(ans,(int)str.length());
}
}
return ans;
}
};
15. 三数之和(双指针)
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
105 <= nums[i] <= 105
我的代码
本题就是很典型的双指针问题,先将数组排序,然后设定双指针,找到目标数组。
注意:此处的坑点在于,要避免重复答案的出现,可以通过while循环来判断,也可以用find函数来判断,不过while循环肯定要高效一点
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
if(nums[nums.size()-1] < 0 || nums[0] > 0) return ans;
if(nums.size() < 3) return ans;
for(int i = 0; i < nums.size() && nums[i] <= 0; i++){
if(i > 0 && nums[i] == nums[i-1]) continue; //跳过重复的值
int left = i+1, right = nums.size()-1;
while(left < right){
if(left < right && nums[i] + nums[left] + nums[right] == 0){
vector<int> temp;
temp.push_back(nums[i]);
temp.push_back(nums[left]);
temp.push_back(nums[right]);
ans.push_back(temp);
while(nums[left] == temp[1] && left< right) left++; //重要,跳过重复的值
while(nums[right] == temp[2] && left < right) right--; // 重要哦,跳过重复的值
}
else if(nums[i] + nums[left] + nums[right] < 0){
left++;
}
else{
right--;
}
}
}
return ans;
}
};
16. 最接近的三数之和(双指针)
题目描述
给你一个长度为 n
的整数数组 nums
**和 一个目标值 target
。请你从 nums
**中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
提示:
3 <= nums.length <= 1000
1000 <= nums[i] <= 1000
104 <= target <= 104
我的代码
其实此题与上一题大同小异,都是利用双指针来求解,最开始也是要对数组进行排序,然后固定一个值,然后设定左右指针,向前和向后查找目标答案。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int best = INT_MAX;
// 根据差值的绝对值来更新答案
auto update = [&](int cur) {
if (abs(cur - target) < abs(best - target)) {
best = cur;
}
};
// 枚举 a
for (int i = 0; i < n; ++i) {
// 保证和上一次枚举的元素不相等
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 使用双指针枚举 b 和 c
int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
// 如果和为 target 直接返回答案
if (sum == target) {
return target;
}
update(sum);
if (sum > target) {
// 如果和大于 target,移动 c 对应的指针
int k0 = k - 1;
// 移动到下一个不相等的元素
while (j < k0 && nums[k0] == nums[k]) {
--k0;
}
k = k0;
} else {
// 如果和小于 target,移动 b 对应的指针
int j0 = j + 1;
// 移动到下一个不相等的元素
while (j0 < k && nums[j0] == nums[j]) {
++j0;
}
j = j0;
}
}
}
return best;
}
};
17.BM20 数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:
[1,2,3,4,5,6,7,0]
返回值:
7
示例2
输入:
[1,2,3]
返回值:
0
我的代码
18.二叉搜索树与双向链表
题目描述
描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
数据范围:输入二叉树的节点数 0≤n≤10000≤n≤1000,二叉树中每个节点的值 0≤val≤10000≤val≤1000要求:空间复杂度�(1)O(1)(即在原树上操作),时间复杂度 O(1)O(n)
注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继2.返回链表中的第一个节点的指针3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:
二叉树的根节点
返回值描述:
双向链表的其中一个头节点。
示例1
输入:
{10,6,14,4,8,12,16}
返回值:
From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
说明:
输入题面图中二叉树,输出的时候将双向链表的头节点返回即可。
示例2
输入:
{5,4,#,3,#,2,#,1}
返回值:
From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;
说明:
5 / 4 / 3 / 2 / 1 树的形状如上图
我的代码
本题采取的是递归方式,TreeConvert(root)函数是构造这个双链表,然后返回这个链表的根。
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree == nullptr) return nullptr;
pRootOfTree = TreeConvert(pRootOfTree);
while(pRootOfTree->left) pRootOfTree = pRootOfTree->left;
return pRootOfTree;
}
TreeNode* TreeConvert(TreeNode* root){
if(root->left == nullptr && root->right == nullptr){
return root;
}
if(root->left){
TreeNode* t;
t = TreeConvert(root->left);
while(t->right) t = t->right;
root->left = t;
t->right = root;
}
if(root->right){
TreeNode* t;
t = TreeConvert(root->right);
while(t->left) t = t->left;
root->right = t;
t->left = root;
}
return root;
}
};
BM50 两数之和
我的代码
哈希表
上述方法的时间复杂度可以进一步优化至O(N)。
哈希表的思想为「以空间换时间」,这是由于哈希表保存了键值对,其「查找」复杂度为O(1)。
对于本题,具体解题思路为(如图所示):
- 定义哈希表hashmap,其存放的键值对为<取值,下标>。
- 从开始处遍历数组,对于第i个位置,在哈希表中寻找target-nums[i]是否存在,若存在,将两个下标放入数组中返回;若不存在,将其添加至表中,继续遍历。
注:在将结果保存至数组中时,无需判断二者下标大小:当所遍历到的位置符合题目要求时,说明哈希表中已经存在待查找的另一元素,因此「已存在于表中的元素的下标」一定小于「当前位置元素的下标」。
class Solution {
public:
/**
*
* @param numbers int整型vector
* @param target int整型
* @return int整型vector
*/
vector<int> twoSum(vector<int>& numbers, int target) {
int n = numbers.size();
vector <int> res;
if (!n)
return res;
// 定义哈希表,unordered_map是用哈希表实现的,复杂度为O(1),而map是用红黑树实现的
unordered_map<int, int> hashmap;
for (int i = 0; i < n; i ++) {
if (hashmap.find(target - numbers[i]) != hashmap.end()) { // find函数返回hashmap.end()代表未找到,否则代表找到
// 将结果存入数组
res.push_back(hashmap[target - numbers[i]] + 1);
res.push_back(i + 1);
break;
} else {
hashmap[numbers[i]] = i; // 将未找到的值插入哈希表中,继续遍历
}
}
return res;
}
};
BM52 数组中只出现一次的两个数字
题目描述
一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
数据范围:数组长度 2≤n≤10002≤n≤1000,数组中每个数的大小 0<val≤10000000<val≤1000000要求:空间复杂度 O(1),时间复杂度 O(n)
提示:输出时按非降序排列。
我的代码
位运算
对于这道题目,我们先来想另外一个问题:
如果数组中只有一个出现了一次的数字,我们想到得到它,那么应该如何解决呢?
我们都知道异或运算:如果两个数一样则异或结果为0,不一样则异或结果为1。(二进制)
(0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0)
举个例子:
4 ⊕ 4 = 0,将4化为二进制为 0100
所以 0100
异或 0100
得到 0000
4 ⊕ 4 ⊕ 5 = 5
则 0100
0100
0101
得到 0101
我们可以看到上面的运算过程,因为4=4,两者相等异或结果为0。所以0异或任意数都等于任意数。
所以,当只有一个出现了一次的数字的时候,则只需要将全部数进行异或运算,运算结果就剩下了那个只出现一次的数字了。
好了,上面说了这么多,那这道题目是找两个只出现一次的数字呀~
上面的方法又是针对只出现一次的数字,假设我也一样全部执行异或运算 1⊕4⊕1⊕6
,最后也还是会剩下4⊕6呀~
我们看看:
0100 ⊕ 0110 = 0010 这个结果也不能得出什么东西哇~
我们换个角度思考,能不能做个分组,将题目分为两组 ,然后每一组求出其中的出现一次的数字,最后两者一起返回,不就解决问题了吗?
那么我们要如何分组呢?位运算进行分组,我们首先想到的应该是奇偶分组,就是将所有数 &1,此时能将数字分为奇偶两组。
但是这个时候问题又来了,你又不能保证两个数字就一奇一偶,有可能都是奇数也有可能都是偶数呀~
但是,我们想一下,&1的操作,归根到底,是按照二进制最低位的不同来分组的,
例如 : 0011(3) ,0101(5),0100(4),0001(1)
对上面四个数分组,我们都&1,可以分得结果: 0011,0101,0001(奇数) 0100 (偶数)
我们很明显能够知道,当二进制&1结果为1的时候,为奇数,反之为偶数。它们是按照最低位的不同来分组的。
上面我们知道,能够将数字分为 奇偶两组,那么现在,我再给出一个难度,如何区分出 0011,0101 ?
对 0011,0101 这两个数进行分组,我们可以观察到最低位都为1,此时如果我们还是进行&1操作去分组,那肯定是分不出来的!
因为两数的最低为都是一样的,&1之后还是1,还是无法区分,那么我们看到最低的第二位0011是1,0101是0,很明显这两位就不一样,那么我们就可以将这两数&0010呀,不就能够区分出来了吗?
0011 &0010 = 0010 0101&0010 = 0000,此时还是根据结果是否为0得到分组!
那要是是 0100 和 1100呢?如何分组呢? 不就是&1000 就能够分组了吗?
**所以,**说了那么多,其实就是为了推出一个分组的方式,两个不同的数如何分组!
我们都知道两个不同的数,那么它的二进制表示肯定是不一样的!这是毋庸置疑的!
所以,我们要想对两者进行分组操作,就是需要找到两者中的那一位不同的二进制,然后得到分组的与值(去&的那个值),问题不就解决了吗?
那要怎么找到那一位不同的二进制呢?
我们看一个例子: 1,1,4,6
全部做异或运算结果为 4⊕6 = 0100⊕0110 = 0010
异或的运算规则是什么? 相同的为0,不同的为1。所以我们根据两者异或出来的结果 0010,不就可以知道那一位不同了嘛?(为1的那一位就是不同的)
好了,说了这么多,下面安排代码把~
public int[] FindNumsAppearOnce (int[] array) {
// 先将全部数进行异或运算,得出最终结果
int tmp = 0;
for(int num: array){
tmp ^= num;
}
// 找到那个可以充当分组去进行与运算的数
// 从最低位开始找起
int mask = 1;
while((tmp&mask) == 0){
mask <<= 1;
}
// 进行分组,分成两组,转换为两组 求出现一次的数字 去求
int a = 0;
int b = 0;
for(int num:array){
if((num&mask) == 0){
a ^= num;
}else{
b ^= num;
}
}
// 因为题目要求小的数放前面,所以这一做个判断
if(a > b){
int c = a;
a = b;
b = c;
}
return new int[]{a,b};
}
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.length();
vector<bool> dp(n + 1,false); // dp[i] 代表前i个字符可以由字典组成的布尔值
dp[0] = true; //前零个字符可以
for(int i = 1; i<= s.length(); i++){
cout << "i:" << i << endl;
for(int j = 0; j < i; j++){
cout << s.substr(j,i-j) << endl;
// 这里取子串要注意,i和j代表的都是字母的个数而非数组下标
if(find(wordDict.begin(),wordDict.end(),s.substr(j,i-j)) != wordDict.end() && dp[j] == true){
cout << "true" << endl;
dp[i] = true;
break;
}
}
cout << endl;
}
return dp[s.length()];
}
};
53. 最大子数组和(动态规划)
题目描述
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
104 <= nums[i] <= 104
**进阶:**如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
我的代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int dp[n+5];
dp[0] = nums[0];
// dp[i]表示以第i个元素为尾部的连续子数组的最大和
// dp[i] 有两种情况,
// 1. nums[i]自成一个子数组
// 2. nums[i]和前i个中的部分成为子数组
// dp[i] 要取得最大值。
for(int i = 1; i < n; i++){
dp[i] = max(dp[i-1] + nums[i],nums[i]);
}
return *max_element(dp,dp+n);
}
};
剑指 Offer II 095. 最长公共子序列(动态规划)
题目描述
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 **是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1
和text2
仅由小写英文字符组成。
我的代码
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
// 深度优先不好做,用动态规划比较好
// dp[i][j] 表示,text1[0...i-1]text2[0...j-1] 这两个子串的最长公共子序列
// 如果text1[i-1] == text2[j-1] 则dp[i][j] = dp[i-1][j-1] +1 ;; 注意动态规划中的i与数组中的i-1相对应
// 如果不等于,那么dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
// 初始条件是:dp[0][0...n] = dp[0...n][0] = 0; //因为一个子串长度为0,那肯定没有公共子序列呀!
vector<vector<int>> dp(text1.length()+1,vector<int>(text2.length()+1,0));
for(int i = 1; i <= text1.length(); i++){
for(int j = 1; j <= text2.length(); j++){
if(text1[i-1] == text2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}
else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
};
KY213 奇偶校验
题目描述
输入一个字符串,然后对每个字符进行奇校验,最后输出校验后的二进制数(如’3’,输出:10110011)。
输入描述:
输入包括一个字符串,字符串长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 对于字符串中的每一个字符,输出按题目进行奇偶校验后的数,每个字符校验的结果占一行。
我的代码
**代码思路:**信息是以比特流的方式传输的,类似01000001。在传输过程中,有可能会发生错误,比如,我们存储了01000001,但是取出来却是01000000,即低位由0变成了1。为了检测到这种错误,我们可以通过“奇偶校验”来实现。假如,我们存储的数据是一个字节,8个比特位,那我们就可以计算每个字节比特位是1的个数,如果是偶数个1,那么,我们就把第九个位设为1,如果是奇数个1,那么就把第九个位设为0,这样连续9个字节比特位为1的位数肯定是奇数。这中方法叫做“奇校验”,“偶校验”和此类似。当然,在实际应用中,也可以把一个字节的前7位作为数据位,最后一个为作为校验位。
比如说对字符‘3’进行奇偶校验。'3’的ascii值为51,51对应二进制为 0110011(用七位表示) 其中1的个数为4(偶数)个。所以在最高为添1 所以’3’的奇校验为10110011。bitset
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main(){
int x;
char a;
while(cin >> a){
x = 0;
int count = 0;
x += a;
while(a){
count += a%2;
a = a/2;
}
if((count % 2) == 0){ //奇数个1
x += 128; // 第七位置为1
}
cout << bitset<8>(x) << '\n';
}
return 0;
}
KY216 遍历链表
题目描述
建立一个升序链表并遍历输出。
输入描述:
输入的每个案例中第一行包括1个整数:n(1<=n<=1000),接下来的一行包括n个整数。
输出描述:
可能有多组测试数据,对于每组数据, 将n个整数建立升序链表,之后遍历链表并输出。
我的代码
方法一:在输入数据时就开始构造一个升序列表,用insert()递归构造;
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
struct Linknode{
int value;
Linknode *next;
Linknode(int value1, Linknode *next1 = nullptr){
value = value1;
next = next1;
}
};
void insert(int x,Linknode *&P){
if(P == nullptr){
P = new Linknode(x);
}
else if(P->value <= x){
insert(x,P->next);
}
else{
int t = P->value;
P->value = x;
insert(t,P->next);
}
}
int main (){
Linknode *numberList = nullptr;
int n;
cin >> n;
for(int i = 0; i < n; i++){
int x;
cin >> x;
insert(x,numberList);
}
for(int i = 0; i < n; i++){
cout << numberList->value << ' ';
numberList = numberList->next;
}
}
法二:利用list的sort函数排序
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
struct Linknode{
int value;
Linknode *next;
Linknode(int value1, Linknode *next1 = nullptr){
value = value1;
next = next1;
}
};
int main (){
Linknode *numberList = nullptr;
list<int> LL;
int n;
cin >> n;
for(int i = 0; i < n; i++){
int x;
cin >> x;
LL.push_back(x);
}
LL.sort();
for(int i = 0; i < n; i++){
cout << LL.front() << ' ';
LL.pop_front();
}
}
[list类KY216 遍历链表 ](https://www.notion.so/list-62e6adab5cb04d61aa540814cceebfe4?pvs=21)
36. 有效的数独
166. 分数到小数(竖式除法)
题目描述
给定两个整数,分别表示分数的分子 numerator
和分母 denominator
,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
如果存在多个答案,只需返回 任意一个 。
对于所有给定的输入,保证 答案字符串的长度小于 104
。
示例 1:
输入:numerator = 1, denominator = 2
输出:"0.5"
示例 2:
输入:numerator = 2, denominator = 1
输出:"2"
示例 3:
输入:numerator = 4, denominator = 333
输出:"0.(012)"
提示:
231 <= numerator, denominator <= 231 - 1
denominator != 0
我的代码
代码思路:本题主要是考察模拟除法以及哈希表的运用,模拟除法如下图所示。
长除法
题目要求根据给定的分子和分母,将分数转成整数或小数。由于给定的分子和分母的取值范围,为了防止计算过程中产生溢出,需要将分子和分母转成 64 位整数表示。
将分数转成整数或小数,做法是计算分子和分母相除的结果。可能的结果有三种:整数、有限小数、无限循环小数。
如果分子可以被分母整除,则结果是整数,将分子除以分母的商以字符串的形式返回即可。
如果分子不能被分母整除,则结果是有限小数或无限循环小数,需要通过模拟长除法的方式计算结果。为了方便处理,首先根据分子和分母的正负决定结果的正负(注意此时分子和分母都不为 0),然后将分子和分母都转成正数,再计算长除法。
计算长除法时,首先计算结果的整数部分,将以下部分依次拼接到结果中:
如果结果是负数则将负号拼接到结果中,如果结果是正数则跳过这一步;
将整数部分拼接到结果中;
将小数点拼接到结果中。
完成上述拼接之后,根据余数计算小数部分。
计算小数部分时,每次将余数乘以 10,然后计算小数的下一位数字,并得到新的余数。重复上述操作直到余数变成 0 或者找到循环节。
如果余数变成 0,则结果是有限小数,将小数部分拼接到结果中。
如果找到循环节,则找到循环节的开始位置和结束位置并加上括号,然后将小数部分拼接到结果中。
如何判断是否找到循环节?注意到对于相同的余数,计算得到的小数的下一位数字一定是相同的,因此如果计算过程中发现某一位的余数在之前已经出现过,则为找到循环节。为了记录每个余数是否已经出现过,需要使用哈希表存储每个余数在小数部分第一次出现的下标。
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
// 为了避免在余数*10的过程中int溢出
long longnumerator = numerator, longdenominator = denominator;
// 当在求余数的过程中,如果遇到了相同余数,则说明出现了循环小数,所以此处用哈希表存储余数和对应的index值
// 存储index值是为了好构造(循环小数)字符串
unordered_map<long,int> yushumap;
string ans,shang,xiaoshu;
// 先判断两个数是否异号,若是则在结果中加入‘-’,然后取绝对值
if((numerator > 0 && denominator < 0) || (numerator < 0 && denominator > 0) )
ans.push_back('-');
// 取绝对值,ans()的头文件位<math.h>
longnumerator = abs(longnumerator);
longdenominator = abs(longdenominator);
// 如果可以直接整除,那么直接用to_string()函数将商转为string,注意此处要添加符号
if(longnumerator % longdenominator == 0) return ans + to_string(longnumerator / longdenominator);// 可以整除,则利用to_string()直接将商转换位string
// 若不能整除,则先将商转为string
shang = to_string(longnumerator/longdenominator);
// 添加小数点
shang.push_back('.');
// 模拟除法,每次余数*10 再继续除
long yushu = longnumerator % longdenominator;
yushu *= 10;
int index = 0; // 记录小数在xiaoshu字符串中的位置
// 处理小数,当余数为零或者找到循环点则退出循环
while(yushu != 0 && !yushumap.count(yushu)){
xiaoshu.push_back(yushu/longdenominator + '0');
yushumap[yushu] = index++;
// 提取新一轮余数
yushu = yushu % longdenominator;
yushu *= 10;
}
// 找到循环点,则处理成(循环小数)的形式
if(yushumap.count(yushu)){
string temp = xiaoshu.substr(0,yushumap[yushu]) + "(" + xiaoshu.substr(yushumap[yushu],index);
temp += ")";
xiaoshu = temp;
}
// 注意此处的 += 是为了添加符号
ans += shang + xiaoshu;
return ans;
}
};
179. 最大数
题目描述
给定一组非负整数 nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
**注意:**输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例 1:
输入:nums = [10,2]输出:"210"
示例 2:
输入:nums = [3,30,34,5,9]输出:"9534330"
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 109
我的代码
首先,我想到了对每个字符进行排序,主要就是要编写sort函数中的参数,cmp函数,但是判断过程比较复杂,花了很长的时间都没能完全AC,看了题解,有一个十分便捷的方法,那就是将两个string分别排序然后直接比较大小。代码如下:
我真是个憨憨
class Solution {
public:
static bool cmp1(string A,string B){
string temp1 = A + B;
string temp2 = B + A;
return temp1 > temp2;
}
string largestNumber(vector<int>& nums) {
string ans;
vector<string> num;
for(int i = 0; i < nums.size(); i++){
num.push_back(to_string(nums[i]));
}
sort(num.begin(),num.end(), cmp1);
if(num[0] == "0") return "0";
for(int i = 0; i < nums.size(); i++){
ans += num[i];
cout << num[i] << endl;
}
return ans;
}
};
331. 验证二叉树的前序序列化(槽位)
题目描述
序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #
。
例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#"
,其中 #
代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
保证 每个以逗号分隔的字符或为一个整数或为一个表示 null
指针的 '#'
。
你可以认为输入格式总是有效的
- 例如它永远不会包含两个连续的逗号,比如
"1,,3"
。
**注意:**不允许重建树。
示例 1:
输入:preorder ="9,3,4,#,#,1,#,#,2,#,6,#,#"输出:true
示例 2:
输入:preorder ="1,#"输出:false
示例 3:
输入:preorder ="9,#,#,1"输出:false
提示:
1 <= preorder.length <= 104
preorder
由以逗号“,”
分隔的[0,100]
范围内的整数和“#”
组成
我的代码
这道题的解题思路很特别:
运用栈:
我们可以定义一个概念,叫做槽位。一个槽位可以被看作「当前二叉树中正在等待被节点填充」的那些位置。
二叉树的建立也伴随着槽位数量的变化。每当遇到一个节点时:
如果遇到了空节点,则要消耗一个槽位;
如果遇到了非空节点,则除了消耗一个槽位外,还要再补充两个槽位。
此外,还需要将根节点作为特殊情况处理。
我们使用栈来维护槽位的变化。栈中的每个元素,代表了对应节点处剩余槽位的数量,而栈顶元素就对应着下一步可用的槽位数量。当遇到空节点时,仅将栈顶元素减 111;当遇到非空节点时,将栈顶元素减 111 后,再向栈中压入一个 222。无论何时,如果栈顶元素变为 000,就立刻将栈顶弹出。
遍历结束后,若栈为空,说明没有待填充的槽位,因此是一个合法序列;否则若栈不为空,则序列不合法。此外,在遍历的过程中,若槽位数量不足,则序列不合法。
class Solution {
public:
bool isValidSerialization(string preorder) {
int n = preorder.length();
stack<int> pos; //维护槽位数,为什么要用栈呢?为了方便随时记录,其实也可以直接用一个int变量来记录
pos.push(1); // 代表栈顶需要一个槽位
int i = 0;
while(i < n && !pos.empty()){
if(preorder[i] == ',') {i++;continue;} // 跳过,
if(preorder[i] == '#'){
pos.top()--; // 消耗一个槽位
if(pos.top() <= 0) pos.pop(); // 如果栈顶槽位为0,则出栈
i++;
continue;
}
while(isdigit(preorder[i])){ // 跳过数字,其实不关注数字具体是多少
i++;
}
pos.top()--;
if(pos.top() <= 0) pos.pop();
pos.push(2);
}
if(pos.empty() && i >= n) return true; // 当树遍历完了,然后槽位位置也刚好为0时,返回true
else return false;
}
};
11. 盛最多水的容器(双指针)
题目描述
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
我的代码
题解:
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int l = 0,r = n-1; // 双指针
int max = (r-l) * min(height[l],height[r]);
while(l < r){
if(height[l] <= height[r]){
l++;
}
else r--;
if(l < r) max = max < ((r-l) * min(height[l],height[r])) ? (r-l) * min(height[l],height[r]) : max;
}
return max;
}
};
640. 求解方程(模拟)
题目描述
求解一个给定的方程,将x
以字符串 "x=#value"
的形式返回。该方程仅包含 '+'
, '-'
操作,变量 x
和其对应系数。
如果方程没有解或存在的解不为整数,请返回 "No solution"
。如果方程有无限解,则返回 “Infinite solutions”
。
题目保证,如果方程中只有一个解,则 ‘x’ 的值是一个整数。
示例 1:
输入: equation = "x+5-3+x=6+x-2"
输出: "x=2"
示例 2:
输入: equation = "x=x"
输出: "Infinite solutions"
示例 3:
输入: equation = "2x=x"
输出: "x=0"
提示:
3 <= equation.length <= 1000
equation
只有一个'='
.- 方程由绝对值在
[0, 100]
范围内且无任何前导零的整数和变量'x'
组成。
我的代码
代码思路其实很简单,就是统计变量x的系数,以及常数值。其次就是什么情况下为No Solution,什么情况下为Infinite solutions
最开始我的思路为,用numl和numr分别统计‘=‘左右两边的整数,Xl和Xr来统计’=‘左右两边的X变量数。但是我思考得还不够全面。没有考虑到以下几种情况:
- 0x,也即x前系数为0得情况
- -2x,x前系数为负数得情况
- -x,-x这一特殊变量
此外,我也没有想清楚什么情况下为No Solution,什么情况下为Infinite solutions:
- 当左右两边的x变量系数相等时,且左右两边常数相等时,为Infinite solutions
- 当左右两边系数相等且左右两边常数不等时,为No Solution
- 其余情况则可以直接相除,得到正确结果。
由于以上原因,我编写代码得过程并不顺利,Bug层出。此处可参考官方思路:
官方代码如下:
class Solution {
public:
string solveEquation(string equation) {
int factor = 0, val = 0;
int index = 0, n = equation.size(), sign1 = 1; // 等式左边默认系数为正
while (index < n) {
if (equation[index] == '=') {
sign1 = -1; // 等式右边默认系数为负
index++;
continue;
}
int sign2 = sign1, number = 0;
bool valid = false; // 记录 number 是否有效
if (equation[index] == '-' || equation[index] == '+') { // 去掉前面的符号
sign2 = (equation[index] == '-') ? -sign1 : sign1;
index++;
}
while (index < n && isdigit(equation[index])) {
number = number * 10 + (equation[index] - '0');
index++;
valid = true;
}
if (index < n && equation[index] == 'x') { // 变量
factor += valid ? sign2 * number : sign2;
index++;
} else { // 数值
val += sign2 * number;
}
}
if (factor == 0) {
return val == 0 ? "Infinite solutions" : "No solution";
}
return string("x=") + to_string(-val / factor);
}
};
注:像这种,从字符串中提取数字的题目,并且涉及到±号的时候,建议用while语句一次性提取出数字,然后向后遍历,如果用for循环,可能不太直观。
31. 下一个排列
题目描述
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3]
,以下这些都可以视作arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]
的下一个排列是[1,3,2]
。 - 类似地,
arr = [2,3,1]
的下一个排列是[3,1,2]
。 - 而
arr = [3,2,1]
的下一个排列是[1,2,3]
,因为[3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
我的代码
代码思路很简单,从数组末尾开始找,找到第一个降序的数据对i-1,i
,然后从末尾升序队列中找到第一个大于第i-1
个数的值j
,交换j与i-1
,然后将末尾剩余的数反转即可。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
if(n == 1){
cout << nums[0] << endl;
return ;
}
int i = n-1,j;
while(i > 0){
if(nums[i] > nums[i-1]) break;
i--;
}
if(i <= 0) reverse(nums.begin(),nums.end());
else{
int key = nums[i-1];
for(j = n-1; j > i-1; j--){
if(nums[j] > key) break;
}
swap(nums[i-1],nums[j]);
// int m = n-1-i+1;
// for(int h = 0; h < m-1; h++){
// for(int x = i; x < n-h-1; x++){
// if(nums[x] > nums[x+1]) swap(nums[x],nums[x+1]);
// }
// }
reverse(nums.begin()+i,nums.end());
}
cout << "[" << nums[0];
for(int h = 1; h < n; h++){
cout << "," << nums[h];
}
cout << "]" << endl;
}
};
最开始我使用的是冒泡排序,但是后来发现,直接就地翻转即可。
200. 岛屿数量(DFS、BFS)
题目描述
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
的值为'0'
或'1'
我的代码
一开始我想到的是动态规划,但是,想半天没有把子问题想清楚,因此,本题不能用动态规划。
思路如下:
思路一:深度优先遍历 DFS
目标是找到矩阵中 “岛屿的数量” ,上下左右相连的 1 都被认为是连续岛屿。
dfs方法: 设目前指针指向一个岛屿中的某一点 (i, j),寻找包括此点的岛屿边界。
从 (i, j) 向此点的上下左右 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 做深度搜索。
终止条件:
- (i, j) 越过矩阵边界;
- grid[i][j] == 0,代表此分支已越过岛屿边界。
搜索岛屿的同时,执行 grid[i][j] = ‘0’,即将岛屿所有节点删除,以免之后重复搜索相同岛屿。
主循环:
遍历整个矩阵,当遇到 grid[i][j] == ‘1’ 时,从此点开始做深度优先搜索 dfs,岛屿数 count + 1 且在深度优先搜索中删除此岛屿。
最终返回岛屿数 count 即可。
思路二:广度优先遍历 BFS
主循环和思路一类似,不同点是在于搜索某岛屿边界的方法不同。
bfs 方法:
借用一个队列 queue,判断队列首部节点 (i, j) 是否未越界且为 1:
若是则置零(删除岛屿节点),并将此节点上下左右节点 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 加入队列;
若不是则跳过此节点;
循环 pop 队列首节点,直到整个队列为空,此时已经遍历完此岛屿。
class Solution {
public:
int m,n;
void dfs(vector<vector<char>>& grid, int i, int j){
if(i < 0 || i >= m || j < 0 || j >= n) return;
if(grid[i][j] == '1'){
grid[i][j] = '0';
dfs(grid,i-1,j);
dfs(grid,i,j-1);
dfs(grid,i+1,j);
dfs(grid,i,j+1);
}
}
void bfs(vector<vector<char>>& grid, int i, int j){
queue<pair<int,int>> que;
if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return;
que.push(pair<int,int>(i,j));
while(!que.empty()){
pair<int,int> pos = que.front();
que.pop();
int ii = pos.first, jj = pos.second;
if(ii < 0 || ii >= m || jj < 0 || jj >= n || grid[ii][jj] == '0') continue;
grid[ii][jj] = '0';
que.push(pair<int,int>(ii,jj-1));
que.push(pair<int,int>(ii-1,jj));
que.push(pair<int,int>(ii,jj+1));
que.push(pair<int,int>(ii+1,jj));
}
}
int numIslands(vector<vector<char>>& grid) {
m = grid.size();
n = grid[0].size();
int ans = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(grid[i][j] == '1'){
ans++;
grid[i][j] = '0';
// dfs(grid,i-1,j);
// dfs(grid,i,j-1);
// dfs(grid,i+1,j);
// dfs(grid,i,j+1);
bfs(grid,i-1,j);
bfs(grid,i,j-1);
bfs(grid,i+1,j);
bfs(grid,i,j+1);
}
}
}
return ans;
}
};
面试题13. 机器人的运动范围(DFS)
题目描述
地上有一个m行n列的方格,从坐标 [0,0]
到坐标 [m-1,n-1]
。一个机器人从坐标 [0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
我的代码
可达解分析:
根据数位和增量公式得知,数位和每逢 进位 突变一次。根据此特点,矩阵中 满足数位和的解 构成的几何形状形如多个 等腰直角三角形 ,每个三角形的直角顶点位于 0,10,20,…0, 10, 20, …0,10,20,… 等数位和突变的矩阵索引处 。
三角形内的解虽然都满足数位和要求,但由于机器人每步只能走一个单元格,而三角形间不一定是连通的,因此机器人不一定能到达,称之为 不可达解 ;同理,可到达的解称为 可达解 (本题求此解) 。
图例展示了 n,m=20n,m = 20n,m=20 , k∈[6,19]k \in [6, 19]k∈[6,19] 的可达解、不可达解、非解,以及连通性的变化。
根据可达解的结构和连通性,易推出机器人可 仅通过向右和向下移动,访问所有可达解 。
三角形内部: 全部连通,易证;
两三角形连通处: 若某三角形内的解为可达解,则必与其左边或上边的三角形连通(即相交),即机器人必可从左边或上边走进此三角形。
class Solution {
public:
bool valid(int k, int x, int y){
int num = 0;
while(x){
num += (x%10);
x /= 10;
}
while(y){
num += (y%10);
y /= 10;
}
if(num > k) return false;
return true;
}
int dfs(int m, int n, int i, int j, int k,vector<vector<int>> &visit){
if(i < 0 || i >= m || j < 0|| j >= n || !valid(k,i,j) || visit[i][j]) return 0;
cout << i << " " << j << endl;
visit[i][j] = 1; // 这个其实就是在记录不可达解
if(i == m-1 && j == n-1){ // 可达解
return 1;
}
return 1 + dfs(m,n,i+1,j,k,visit) + dfs(m,n,i-1,j,k,visit) + dfs(m,n,i,j+1,k,visit) + dfs(m,n,i,j-1,k,visit);
}
int movingCount(int m, int n, int k) {
vector<vector<int>> visit(m,vector<int>(n,0));
return dfs(m,n,0,0,k,visit);
}
};
297. 二叉树的序列化与反序列化
题目描述
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例 1:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
提示:
- 树中结点数在范围
[0, 104]
内 1000 <= Node.val <= 1000
题解(本题思路不难,但是我没过,超时了,此处贴题解)
45. 跳跃游戏 II(贪心)
题目描述
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是2。
从下标为 0 跳到下标为 1 的位置,跳1 步,然后跳3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
- 题目保证可以到达
nums[n-1]
我的代码
方法二:正向查找可到达的最大位置
方法一虽然直观,但是时间复杂度比较高,有没有办法降低时间复杂度呢?
如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
例如,对于数组 [2,3,1,2,4,2,3],初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。
从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。
在具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1。
在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
class Solution {
public:
int jump(vector<int>& nums) {
int ans = 0;
int i = 0,max_pos=0,max_index = 0;
// max_pos代表当走到max_index时,最远可以走的位置,它可以大于等于nums的长度
// 因为只有max_pos大于nums的长度时,才可能走到结尾。
if(nums[0] == 0) return ans;
if(nums.size() == 1) return 0; // 当只有一个节点时,根本不需要跳即可到达终点
while(i < nums.size()-1){
// 为什么要去掉最后一个节点呢?因为如果可以走到最后一个节点,那么就会使得ans多计算一次
for(int j = 1; j <= nums[i]; j++){
if(i + j >= nums.size()-1) // 已经可以到达终点了,那么直接返回结果
return ans+1;
if(i + j < nums.size()-1){
if(i+j+nums[i+j] > max_pos){
max_pos = i+j+nums[i+j];
max_index = j+i;
}
}
}
cout << max_pos << endl;
cout << max_index << endl;
ans++;
i = max_index;
}
return ans;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置(二分查找)
题目描述
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 105
109 <= nums[i] <= 109
nums
是一个非递减数组109 <= target <= 109
我的代码
本题是一道很典型的二分查找问题,因为所给的数据保证了非递减序列。
代码1
因此就有了第一段代码,由于最开始并不了解我自己背的二分查找的模板查找出来的位置有什么特点,因此,起开始的代码思路如下:
- 通过Binarysearch查找到目标target的位置,若找到,则从该点出发,分别往左和往右找到边界
class Solution {
public:
int Binarysearch(vector<int> nums, int key){
int l = 0,r = nums.size()-1,mid;
while(l < r){
mid = (l+r) /2;
if(key > nums[mid]) l = mid+1;
else r = mid;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(nums.empty()){
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
int pos = Binarysearch(nums,target),left = pos-1,right = pos+1;
cout << pos << endl;
if(nums[pos] != target){ // 未找到
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
while(left >= 0 && nums[left] == target){
left--;
} // 找到左边界-1
left++;
while(right < nums.size() && nums[right] == target){
right++;
}// 右边界+1
right--;
ans.push_back(left);
ans.push_back(right);
return ans;
}
};
代码2
但是当我了解了该模板的特性后,就有了以下修改二分查找
- 可以通过Binarysearch直接查找到边界
- 左边界正常查找,右边界通过查找target+1来确定
class Solution {
public:
int Binarysearch(vector<int> nums, int key){
int l = 0,r = nums.size()-1,mid;
while(l < r){
mid = (l+r) /2;
if(key > nums[mid]) l = mid+1;
else r = mid;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(nums.empty()){
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
int left = Binarysearch(nums,target);
int right = Binarysearch(nums,target+1)-1;
if(nums[left] != target) {
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
if(right + 1 < nums.size() && nums[right+1] == target) right++;
ans.push_back(left);
ans.push_back(right);
return ans;
}
};
187. 重复的DNA序列(哈希、滑动窗口、位运算)
题目描述
DNA序列 由一系列核苷酸组成,缩写为 'A'
, 'C'
, 'G'
和 'T'
.。
- 例如,
"ACGAATTCCG"
是一个 DNA序列 。
在研究 DNA 时,识别 DNA 中的重复序列非常有用。
给定一个表示 DNA序列 的字符串 s
,返回所有在 DNA 分子中出现不止一次的 长度为 10
的序列(子字符串)。你可以按 任意顺序 返回答案。
示例 1:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC","CCCCCAAAAA"]
示例 2:
输入:s = "AAAAAAAAAAAAA"
输出:["AAAAAAAAAA"]
提示:
0 <= s.length <= 105
s[i]=='A'
、'C'
、'G'
or'T'
我的代码
代码1
其实这道题很简单,直接从0开始到n-10,把每个子串遍历一遍,然后用哈希表来存储每种字串出现的频度。统计频度大于1的子串。
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
int n = s.length();
vector<string> ans;
if(n <= 10) return ans;
map<string,int> strMap;
for(int i = 0; i <= n-10; i++){
string temp = s.substr(i,10);
if(strMap.count(temp) > 0){ // 找到
cout << temp << "++";
strMap[temp]++;
if(strMap[temp] == 2) ans.push_back(temp);
cout << strMap[temp] << endl;
}
else strMap[temp] = 1;
}
// auto it = strMap.begin();
// while(it != strMap.end()){
// if(it->second > 1 && find(ans.begin(),ans.end(),it->first) == ans.end()){
// ans.push_back(it->first);
// }
// it++;
// }
return ans;
}
};
以上代码有一个非常棒的思路:if(strMap[temp] == 2) ans.push_back(temp);
可以看到,这一句话就能替代下边一整个循环
代码2
代码二的思路是滑动窗口+哈希表+位运算,简单来说,就是将每个字母设置一个2位的2进制数,然后将10个2位2进制数组成一个int(只用低20位)然后用滑动窗口的思想来在常数时间内计算出下一个字符串的int值。提高时间复杂度。
207. 课程表(拓扑排序,广度优先BFS)
题目描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
我的代码
代码思路
其实,拿到这道题,没有想到过是拓扑排序,此外,我也没写过拓扑排序。因此还是看了题解,题解如下:
一共有 n 门课要上,编号为 0 ~ n-1。
先决条件 [1, 0],意思是必须先上课 0,才能上课 1。
给你 n、和一个先决条件表,请你判断能否完成所有课程。
再举个生活的例子
先穿内裤再穿裤子,先穿打底再穿外套,先穿衣服再戴帽子,是约定俗成的。
内裤外穿、光着身子戴帽子等,都会有点奇怪。
我们遵循穿衣的一条条先后规则,用一串 顺序行为,把衣服一件件穿上。
我们遵循课程之间的先后规则,找到一种上课顺序,把所有课一节节上完。
用有向图描述依赖关系
示例:n = 6,先决条件表:[[3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4]]
课 0, 1, 2 没有先修课,可以直接选。其余的课,都有两门先修课。
我们用有向图来展现这种依赖关系(做事情的先后关系):
这种叫 有向无环图,把一个 有向无环图 转成 线性的排序 就叫 拓扑排序。
有向图有 入度 和 出度 的概念:
如果存在一条有向边 A --> B,则这条边给 A 增加了 1 个出度,给 B 增加了 1 个入度。
所以,顶点 0、1、2 的入度为 0。顶点 3、4、5 的入度为 2。
每次只能选你能上的课
每次只能选入度为 0 的课,因为它不依赖别的课,是当下你能上的课。
假设选了 0,课 3 的先修课少了一门,入度由 2 变 1。
接着选 1,导致课 3 的入度变 0,课 4 的入度由 2 变 1。
接着选 2,导致课 4 的入度变 0。
现在,课 3 和课 4 的入度为 0。继续选入度为 0 的课……直到选不到入度为 0 的课。
这很像 BFS
让入度为 0 的课入列,它们是能直接选的课。
然后逐个出列,出列代表着课被选,需要减小相关课的入度。
如果相关课的入度新变为 0,安排它入列、再出列……直到没有入度为 0 的课可入列。
BFS 前的准备工作
每门课的入度需要被记录,我们关心入度值的变化。
课程之间的依赖关系也要被记录,我们关心选当前课会减小哪些课的入度。
因此我们需要选择合适的数据结构,去存这些数据:
入度数组:课号 0 到 n - 1 作为索引,通过遍历先决条件表求出对应的初始入度。
邻接表:用哈希表记录依赖关系(也可以用二维矩阵,但有点大)
key:课号
value:依赖这门课的后续课(数组)
怎么判断能否修完所有课?
BFS 结束时,如果仍有课的入度不为 0,无法被选,完成不了所有课。否则,能找到一种顺序把所有课上完。
或者:用一个变量 count 记录入列的顶点个数,最后判断 count 是否等于总课程数。
代码如下
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int,vector<int>> graph(numCourses); //图
vector<int> inDegree(numCourses,0); // 入度数组
for(int i = 0; i < prerequisites.size(); i++){
inDegree[prerequisites[i][0]]++;
// 初始化邻接表
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
queue<int> course;
int num = 0;
for(int i =0; i< numCourses; i++){
// 将入度为零的点进队列
if(inDegree[i] == 0) course.push(i);
}
while(!course.empty()){
int course_number = course.front();
course.pop();
// 选了一门入度为0的课程
num++;
// 获得与他相关的课程(也即邻接点),然后对应的入度-1
vector<int> points = graph[course_number];
for(int i = 0; i < points.size(); i++){
inDegree[points[i]]--;
// 若该课程入度减为零,则可以被选择,进入队列等待被选
if(inDegree[points[i]] == 0) course.push(points[i]);
}
}
if(num == numCourses) return true;
else return false;
}
};
1584. 连接所有点的最小费用(克鲁斯卡尔,并查集)
题目描述
给你一个points
数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]
。
连接点 [xi, yi]
和点 [xj, yj]
的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj|
,其中 |val|
表示 val
的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
示例 1:
输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:
我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。
示例 2:
输入:points = [[3,12],[-2,5],[-4,1]]
输出:18
示例 3:
输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4
示例 4:
输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000
示例 5:
输入:points = [[0,0]]
输出:0
提示:
1 <= points.length <= 1000
106 <= xi, yi <= 106
- 所有点
(xi, yi)
两两不同。
代码思路
这个题过于难了,就掌握以下基本思路就行,考到我认了。移步链接👇
8. 字符串转换整数 (atoi)
题目描述
请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi
函数)。
函数 myAtoi(string s)
的算法如下:
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
。 - 返回整数作为最终结果。
注意:
- 本题中的空白字符只包括空格字符
' '
。 - 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
示例 1:
输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"42"(读入 "42")
^
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。
示例 2:
输入:s = " -42"
输出:-42
解释:
第 1 步:"-42"(读入前导空格,但忽视掉)
^
第 2 步:"-42"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:"-42"(读入 "42")
^
解析得到整数 -42 。
由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。
示例 3:
输入:s = "4193 with words"
输出:4193
解释:
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。
提示:
0 <= s.length <= 200
s
由英文字母(大写和小写)、数字(0-9
)、' '
、'+'
、'-'
和'.'
组成
我的代码
本题思路十分简单,就是处理字符串问题,难点在于如何处理最大INT和最小INT,本题采用了一下思路:
- 首先将要转换的数字num设置为long型——为了避免在进行stringtoint转换时出现溢出问题。
- 然后处理正负号
- 溢出判断——num - 1 ≥ INT_MAX,这里为什么这样写呢?
- 因为sting没有正负号,正负号存在了变量positive中,如果为负数,则最小值为-232,整数最大值为232-1,
- 如果num≥ 2^32且符号为正,则判断正溢出
- 如果num > 2^32 且符号为负,则判断负溢出
class Solution {
public:
int myAtoi(string s) {
int pos = 0,flag = 0;
long ans = 0;
bool positive = true;
if(s[pos] == ' '){
while(pos < s.size() && s[pos] == ' ') pos++;
}
if(s[pos] == '-'){
positive = false;
}
if(s[pos] == '+' || s[pos] == '-') pos++;
while((s[pos] >= '0' && s[pos] <= '9') && (pos < s.size()) ){
flag = 1;
ans = ans*10+s[pos]-'0';
cout << ans << endl;
if(ans-1 >= INT_MAX && !positive) {
return INT_MIN;
}
else if(ans > INT_MAX && positive){
return INT_MAX;
}
pos++;
}
if(!positive) ans *= -1;
return ans;
}
};
3. 无重复字符的最长子串(滑动窗口)
题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入:s = "abcabcbb"
输出:3
解释: 因为无重复字符的最长子串是"abc",所以其长度为 3。
示例 2:
输入:s = "bbbbb"
输出:1
解释:因为无重复字符的最长子串是"b",所以其长度为 1。
示例 3:
输入:s = "pwwkew"
输出:3
解释:因为无重复字符的最长子串是"wke",所以其长度为 3。
请注意,你的答案必须是子串的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
我的代码
本题思路就是滑动窗口,当新加入的字母满足条件时,窗口长度增加,当不满足时,则窗口左边出一个元素,右边进一个元素,继续查看(保持最长长度)。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
string str; // 滑动窗口(动态的)
int ans = 1;
if(s.length() == 0) return 0;
str.push_back(s[0]);
int left = 0,right = left+1;
while(right < s.length()){
if(str.find(s[right]) != -1) // 有重复
{
ans = max(ans,(int)str.length());
str.erase(str.begin(),str.begin()+1);
}
else{
str.push_back(s[right]);
right++;
ans = max(ans,(int)str.length());
}
}
return ans;
}
};
15. 三数之和(双指针)
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
105 <= nums[i] <= 105
我的代码
本题就是很典型的双指针问题,先将数组排序,然后设定双指针,找到目标数组。
注意:此处的坑点在于,要避免重复答案的出现,可以通过while循环来判断,也可以用find函数来判断,不过while循环肯定要高效一点
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
if(nums[nums.size()-1] < 0 || nums[0] > 0) return ans;
if(nums.size() < 3) return ans;
for(int i = 0; i < nums.size() && nums[i] <= 0; i++){
if(i > 0 && nums[i] == nums[i-1]) continue; //跳过重复的值
int left = i+1, right = nums.size()-1;
while(left < right){
if(left < right && nums[i] + nums[left] + nums[right] == 0){
vector<int> temp;
temp.push_back(nums[i]);
temp.push_back(nums[left]);
temp.push_back(nums[right]);
ans.push_back(temp);
while(nums[left] == temp[1] && left< right) left++; //重要,跳过重复的值
while(nums[right] == temp[2] && left < right) right--; // 重要哦,跳过重复的值
}
else if(nums[i] + nums[left] + nums[right] < 0){
left++;
}
else{
right--;
}
}
}
return ans;
}
};
16. 最接近的三数之和(双指针)
题目描述
给你一个长度为 n
的整数数组 nums
**和 一个目标值 target
。请你从 nums
**中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
提示:
3 <= nums.length <= 1000
1000 <= nums[i] <= 1000
104 <= target <= 104
我的代码
其实此题与上一题大同小异,都是利用双指针来求解,最开始也是要对数组进行排序,然后固定一个值,然后设定左右指针,向前和向后查找目标答案。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int best = INT_MAX;
// 根据差值的绝对值来更新答案
auto update = [&](int cur) {
if (abs(cur - target) < abs(best - target)) {
best = cur;
}
};
// 枚举 a
for (int i = 0; i < n; ++i) {
// 保证和上一次枚举的元素不相等
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 使用双指针枚举 b 和 c
int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
// 如果和为 target 直接返回答案
if (sum == target) {
return target;
}
update(sum);
if (sum > target) {
// 如果和大于 target,移动 c 对应的指针
int k0 = k - 1;
// 移动到下一个不相等的元素
while (j < k0 && nums[k0] == nums[k]) {
--k0;
}
k = k0;
} else {
// 如果和小于 target,移动 b 对应的指针
int j0 = j + 1;
// 移动到下一个不相等的元素
while (j0 < k && nums[j0] == nums[j]) {
++j0;
}
j = j0;
}
}
}
return best;
}
};
17.BM20 数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:
[1,2,3,4,5,6,7,0]
返回值:
7
示例2
输入:
[1,2,3]
返回值:
0
我的代码
18.二叉搜索树与双向链表
题目描述
描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
数据范围:输入二叉树的节点数 0≤n≤10000≤n≤1000,二叉树中每个节点的值 0≤val≤10000≤val≤1000要求:空间复杂度�(1)O(1)(即在原树上操作),时间复杂度 O(1)O(n)
注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继2.返回链表中的第一个节点的指针3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:
二叉树的根节点
返回值描述:
双向链表的其中一个头节点。
示例1
输入:
{10,6,14,4,8,12,16}
返回值:
From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
说明:
输入题面图中二叉树,输出的时候将双向链表的头节点返回即可。
示例2
输入:
{5,4,#,3,#,2,#,1}
返回值:
From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;
说明:
5 / 4 / 3 / 2 / 1 树的形状如上图
我的代码
本题采取的是递归方式,TreeConvert(root)函数是构造这个双链表,然后返回这个链表的根。
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree == nullptr) return nullptr;
pRootOfTree = TreeConvert(pRootOfTree);
while(pRootOfTree->left) pRootOfTree = pRootOfTree->left;
return pRootOfTree;
}
TreeNode* TreeConvert(TreeNode* root){
if(root->left == nullptr && root->right == nullptr){
return root;
}
if(root->left){
TreeNode* t;
t = TreeConvert(root->left);
while(t->right) t = t->right;
root->left = t;
t->right = root;
}
if(root->right){
TreeNode* t;
t = TreeConvert(root->right);
while(t->left) t = t->left;
root->right = t;
t->left = root;
}
return root;
}
};
BM50 两数之和
我的代码
哈希表
上述方法的时间复杂度可以进一步优化至O(N)。
哈希表的思想为「以空间换时间」,这是由于哈希表保存了键值对,其「查找」复杂度为O(1)。
对于本题,具体解题思路为(如图所示):
- 定义哈希表hashmap,其存放的键值对为<取值,下标>。
- 从开始处遍历数组,对于第i个位置,在哈希表中寻找target-nums[i]是否存在,若存在,将两个下标放入数组中返回;若不存在,将其添加至表中,继续遍历。
注:在将结果保存至数组中时,无需判断二者下标大小:当所遍历到的位置符合题目要求时,说明哈希表中已经存在待查找的另一元素,因此「已存在于表中的元素的下标」一定小于「当前位置元素的下标」。
class Solution {
public:
/**
*
* @param numbers int整型vector
* @param target int整型
* @return int整型vector
*/
vector<int> twoSum(vector<int>& numbers, int target) {
int n = numbers.size();
vector <int> res;
if (!n)
return res;
// 定义哈希表,unordered_map是用哈希表实现的,复杂度为O(1),而map是用红黑树实现的
unordered_map<int, int> hashmap;
for (int i = 0; i < n; i ++) {
if (hashmap.find(target - numbers[i]) != hashmap.end()) { // find函数返回hashmap.end()代表未找到,否则代表找到
// 将结果存入数组
res.push_back(hashmap[target - numbers[i]] + 1);
res.push_back(i + 1);
break;
} else {
hashmap[numbers[i]] = i; // 将未找到的值插入哈希表中,继续遍历
}
}
return res;
}
};
BM52 数组中只出现一次的两个数字
题目描述
一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
数据范围:数组长度 2≤n≤10002≤n≤1000,数组中每个数的大小 0<val≤10000000<val≤1000000要求:空间复杂度 O(1),时间复杂度 O(n)
提示:输出时按非降序排列。
我的代码
位运算
对于这道题目,我们先来想另外一个问题:
如果数组中只有一个出现了一次的数字,我们想到得到它,那么应该如何解决呢?
我们都知道异或运算:如果两个数一样则异或结果为0,不一样则异或结果为1。(二进制)
(0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0)
举个例子:
4 ⊕ 4 = 0,将4化为二进制为 0100
所以 0100
异或 0100
得到 0000
4 ⊕ 4 ⊕ 5 = 5
则 0100
0100
0101
得到 0101
我们可以看到上面的运算过程,因为4=4,两者相等异或结果为0。所以0异或任意数都等于任意数。
所以,当只有一个出现了一次的数字的时候,则只需要将全部数进行异或运算,运算结果就剩下了那个只出现一次的数字了。
好了,上面说了这么多,那这道题目是找两个只出现一次的数字呀~
上面的方法又是针对只出现一次的数字,假设我也一样全部执行异或运算 1⊕4⊕1⊕6
,最后也还是会剩下4⊕6呀~
我们看看:
0100 ⊕ 0110 = 0010 这个结果也不能得出什么东西哇~
我们换个角度思考,能不能做个分组,将题目分为两组 ,然后每一组求出其中的出现一次的数字,最后两者一起返回,不就解决问题了吗?
那么我们要如何分组呢?位运算进行分组,我们首先想到的应该是奇偶分组,就是将所有数 &1,此时能将数字分为奇偶两组。
但是这个时候问题又来了,你又不能保证两个数字就一奇一偶,有可能都是奇数也有可能都是偶数呀~
但是,我们想一下,&1的操作,归根到底,是按照二进制最低位的不同来分组的,
例如 : 0011(3) ,0101(5),0100(4),0001(1)
对上面四个数分组,我们都&1,可以分得结果: 0011,0101,0001(奇数) 0100 (偶数)
我们很明显能够知道,当二进制&1结果为1的时候,为奇数,反之为偶数。它们是按照最低位的不同来分组的。
上面我们知道,能够将数字分为 奇偶两组,那么现在,我再给出一个难度,如何区分出 0011,0101 ?
对 0011,0101 这两个数进行分组,我们可以观察到最低位都为1,此时如果我们还是进行&1操作去分组,那肯定是分不出来的!
因为两数的最低为都是一样的,&1之后还是1,还是无法区分,那么我们看到最低的第二位0011是1,0101是0,很明显这两位就不一样,那么我们就可以将这两数&0010呀,不就能够区分出来了吗?
0011 &0010 = 0010 0101&0010 = 0000,此时还是根据结果是否为0得到分组!
那要是是 0100 和 1100呢?如何分组呢? 不就是&1000 就能够分组了吗?
**所以,**说了那么多,其实就是为了推出一个分组的方式,两个不同的数如何分组!
我们都知道两个不同的数,那么它的二进制表示肯定是不一样的!这是毋庸置疑的!
所以,我们要想对两者进行分组操作,就是需要找到两者中的那一位不同的二进制,然后得到分组的与值(去&的那个值),问题不就解决了吗?
那要怎么找到那一位不同的二进制呢?
我们看一个例子: 1,1,4,6
全部做异或运算结果为 4⊕6 = 0100⊕0110 = 0010
异或的运算规则是什么? 相同的为0,不同的为1。所以我们根据两者异或出来的结果 0010,不就可以知道那一位不同了嘛?(为1的那一位就是不同的)
好了,说了这么多,下面安排代码把~
public int[] FindNumsAppearOnce (int[] array) {
// 先将全部数进行异或运算,得出最终结果
int tmp = 0;
for(int num: array){
tmp ^= num;
}
// 找到那个可以充当分组去进行与运算的数
// 从最低位开始找起
int mask = 1;
while((tmp&mask) == 0){
mask <<= 1;
}
// 进行分组,分成两组,转换为两组 求出现一次的数字 去求
int a = 0;
int b = 0;
for(int num:array){
if((num&mask) == 0){
a ^= num;
}else{
b ^= num;
}
}
// 因为题目要求小的数放前面,所以这一做个判断
if(a > b){
int c = a;
a = b;
b = c;
}
return new int[]{a,b};
}