leetcode (c++, python) [パート 1]
leetcode (c++) [中]
記事のプレビュー:
参考ブラシ質問リンクコードランダムな考え
単調なスタック
739.毎日の気温
第一幕:
制限時間を超えた暴力
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result(temperatures.size(),0);
for(int i=0;i<temperatures.size();i++){
for(int j=i+1;j<temperatures.size();j++){
if(temperatures[j]>temperatures[i]){
result[i]=j-i;
break;
}
}
}
return result;
}
};
方法 2: 単調スタック スタック
は、スタックの最上位要素を取得する前に、まずスタックが空かどうかを判断する必要があります。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
// 递增栈 栈内放元素下标,因为要计算距离
stack<int> st;
vector<int> result(temperatures.size(), 0);
st.push(0);//先放入第一个元素
for(int i=1;i<temperatures.size();i++){
if(temperatures[i]<=temperatures[st.top()]){
st.push(i);
}
else{
while(!st.empty()&&temperatures[i]>temperatures[st.top()]){
//判断空必须放在前面
result[st.top()]=i-st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};
496. 次の偉大な要素 I
方法 1: 単調スタック
nums2 に単調スタックを使用すると、nums2 の各要素のうち次に大きい要素を取得し、ループ内の nums1 に nums2[i] が現れるかどうかを判断できます。これは、nums1 が nums2 のサブセットであるためです。
書き方1:unowned_mapを使ってnums1の要素の値と添字を記録する
umapを使うと見つけやすい
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size(),-1);
stack<int> st;//存放nums的元素值
unordered_map<int, int> umap;
for(int i=0;i<nums1.size();i++){
umap[nums1[i]]=i;//key为元素值,value为下标
}
st.push(nums2[0]);
for(int i=1;i<nums2.size();i++){
while(!st.empty()&&nums2[i]>st.top()){
if(umap.find(st.top())!=umap.end()){
//说明在nums1中找到了
result[umap[st.top()]]=nums2[i];
}
st.pop();
}
st.push(nums2[i]);
}
return result;
}
};
書き方2:find関数を直接使ってnums1を検索し
、イテレータを減算して距離を求める
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size(),-1);
stack<int> st;//存放nums的元素值
st.push(nums2[0]);
for(int i=1;i<nums2.size();i++){
while(!st.empty()&&nums2[i]>st.top()){
auto it=find(nums1.begin(),nums1.end(),st.top());
if(it!=nums1.end()){
//说明在nums1中找到了
result[it-nums1.begin()]=nums2[i];
}
st.pop();
}
st.push(nums2[i]);
}
return result;
}
};
503. 次の偉大な要素 II
方法 1: 単調スタック
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//使用单调栈
stack<int> st;
vector<int> new_nums;
//拼接成一个新数组
for(int i=0;i<nums.size();i++){
new_nums.push_back(nums[i]);
}
for(int i=0;i<nums.size();i++){
new_nums.push_back(nums[i]);
}
vector<int> result(new_nums.size(),-1);
st.push(0);
for(int i=1;i<new_nums.size();i++){
while(!st.empty()&&new_nums[i]>new_nums[st.top()]){
result[st.top()]=new_nums[i];
st.pop();
}
st.push(i);
}
result.resize(new_nums.size()/2);
return result;
}
};
42. 雨水を受ける
方法 1: ダブル ポインター。時間計算量は O(n^2) です。空間複雑度は O(1) です。列ごとに検索して、左側の最も高い列と右側の最も高い列の間で最も短い列の高さを見つけます。
制限時間を超えました
class Solution {
public:
int trap(vector<int>& height) {
//使用双指针
int result=0;
for(int i=0;i<height.size();i++){
if(i==0||i==height.size()-1) continue;//第一个和最后一个不接水
int right_maxh=0;
for(int j=i+1;j<height.size();j++){
//找右边最大值
if(height[j]>right_maxh) right_maxh=height[j];
}
int left_maxh=0;
for(int j=0;j<i;j++){
//找i左边最高值
if(height[j]>left_maxh) left_maxh=height[j];
}
int h=min(left_maxh,right_maxh)-height[i];
if(h > 0) result+=h;
}
return result;
}
};
方法 2: 動的プログラミング
現在の柱の雨水面積: min (左の柱の最高の高さ、右の柱の最高の高さを記録) - 現在の柱の高さ。
両側の最高の高さを取得するには、ダブル ポインタを使用して各列を両側に移動し、実際に計算を繰り返します。各位置の左側の最高の高さ (現在位置を含む) を配列 (maxLeft) に記録し、右側の最高の高さ (現在位置を含む) を配列 (maxRight) に記録します。これにより、動的プログラミングを使用する二重計算が回避されます。
現在地を含める理由:最高の高さが現在地と同じ場合、水を入れることができないことを意味し、計算された面積は0となり、以降の計算に影響を与えず、判断する必要はありません。サイズ
class Solution {
public:
int trap(vector<int>& height) {
//使用动态规划计算每个位置左边和右边最高柱子高度
if (height.size() <= 2) return 0;
vector<int> maxLeft(height.size(), 0);//maxLeft[i]:i位置前(包括i)的最高柱子
vector<int> maxRight(height.size(), 0);//maxRight[i]:i位置后(包括i)的最高柱子
maxLeft[0]=height[0];
int size=height.size();
for(int i=1;i<size;i++){
maxLeft[i]=max(height[i],maxLeft[i-1]);
}
maxRight[size-1]=height[size-1];
for(int i=size-2;i>=0;i--){
maxRight[i]=max(height[i],maxRight[i+1]);
}
int result=0;
for(int i=0;i<size;i++){
int h=min(maxLeft[i],maxRight[i])-height[i];
result+=h;
}
return result;
}
};
方法 3: 単調スタック
水平方向の計算なので、同じ高さの列をスキップする必要があります。そうしないと、面積の計算が繰り返されます。
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0; // 可以不加
stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
if (height[i] < height[st.top()]) {
// 情况一
st.push(i);
}
else if (height[i] == height[st.top()]) {
// 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else {
// 情况三
while (!st.empty() && height[i] > height[st.top()]) {
// 注意这里是while
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}
};
右側の列については h = min(height[st.top()], height[i]) - height[mid] が 0 として計算されるため、同じ高さの列をスキップせずに渡すこともできます。柱の左側の柱はそれ自体と同じ高さであるため、同じ高さの柱です。
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0; // 可以不加
stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
if (height[i] <= height[st.top()]) {
// 情况一
st.push(i);
}
// else if (height[i] == height[st.top()]) { // 情况二
// st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
// st.push(i);
// }
else {
// 情况三
while (!st.empty() && height[i] > height[st.top()]) {
// 注意这里是while
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}
};
84. ヒストグラムの最大の長方形
ピットを踏むというアイデア:最初は一番高い柱を見つけて、その一番高い柱の両側から伸ばすことを考えましたが、最大の長方形に最も高い柱が含まれるとは限りません
アイデアの分析:
各列をトラバースし、現在の列 i の高さを長方形の高さとみなして、長方形の幅の境界から、高さが現在の列 i よりも小さい最初の列を左に見つけます。を選択し、現在のバー i よりも高さが低いバーの右側の最初の列を見つけます。
各列について、上記と同様に現在の列を高さとした長方形の面積を計算し、最後に最大の長方形の面積を比較します。
方法 1: 動的プログラミング
現在の i が大きいときに 2 つの配列を使用して行列の境界を記録し、長方形の幅を計算できるようにします。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int size=heights.size();
vector<int> left(size,0);//左边第一个高度小于当前柱体 i 的柱体的下标
vector<int> right(size,0);//右边第一个高度小于当前柱体 i 的柱体的下标
left[0]=-1;//防止while死循环
for(int i=1;i<size;i++){
//向左查找
int t=i-1;
while(t>=0&&heights[i]<=heights[t]){
//这里有等号
t=left[t];//这里并不是通过t--来找,t--这样比较费时
}
left[i]=t;
}
right[size-1]=size;//防止while死循环
for(int i=size-2;i>=0;i--){
//向右查找
int t=i+1;
while(t<size&&heights[i]<=heights[t]){
t=right[t];
}
//如果右边没有比当前位置小的,那right[i]=size
//也就是当前位置作为高时,右边的所有柱子都可以和i组成矩形
right[i]=t;
}
int result=0;
for(int i=0;i<size;i++){
int sum=right[i]-left[i]-1;//矩形的宽
result=max(sum*heights[i],result);
}
return result;
}
};
方法 2: 単調
スタック スタックの一番下からスタックの一番上まで、順序は増加します。while ループに入ると、スタックの一番上の次の要素は、i より小さい左側の最初の高さになります。 、スタック ヘッドは右側の最初の高さです。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//单调栈,栈底到栈顶为递增顺序
heights.insert(heights.begin(), 0); // 数组头部加入元素0
heights.push_back(0); // 数组尾部加入元素0
int size=heights.size();
stack<int> st;
st.push(0);
int result=0;
for(int i=1;i<size;i++){
if(heights[i]>=heights[st.top()]){
st.push(i);
}
else{
while(!st.empty() && heights[i]<heights[st.top()]){
int mid=st.top();
st.pop();
int w=i-st.top()-1;
result=max(result,w*heights[mid]);
}
st.push(i);
}
}
return result;
}
};
余計な話題
1365. 現在の数字より小さい数字はいくつありますか
第一法則: 暴力
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> result(nums.size(),0);
for(int i=0;i<nums.size();i++){
for(int j=0;j<nums.size();j++){
if(j==i) continue;
if(nums[j]<nums[i]) result[i]++;
}
}
return result;
}
};
941. 有効な山の配列
方法 1: arr の最大値を求めてから、両側の状況を判断する
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
if(arr.size()<3) return false;
int max_num=0;
int index=0;
for(int i=0;i<arr.size();i++){
if(max_num<arr[i]){
max_num=arr[i];
index=i;
}
}
if(index==0||index==arr.size()-1) return false;
for(int i=0;i<index;i++){
if(arr[i]>=arr[i+1]) return false;
}
for(int i=index;i<arr.size()-1;i++){
if(arr[i]<=arr[i+1]) return false;
}
return true;
}
};
方法 2: ダブル ポインター
左から右に厳密に増加する最高点を見つけ、右から左に厳密に増加する最高点を見つけて、2 つの位置が等しいかどうかを比較します。
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
int left=0;
int right=arr.size()-1;
while(left<arr.size()-1&&arr[left]<arr[left+1]){
left++;
}
while(right>0&&arr[right]<arr[right-1]){
right--;
}
if(left==right&&left!=0&&right!=arr.size()-1) return true;
return false;
}
};
1207. ユニークな出来事
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
unordered_map<int,int> m;
for(int i=0;i<arr.size();i++){
//统计出现次数
m[arr[i]]++;
}
map<int,int> count_m;//key不允许有重复的
for(auto it=m.begin();it!=m.end();it++){
count_m[it->second]++;
}
if(m.size()!=count_m.size()) return false;
return true;
}
};
189. 配列を回転する
方法 1: 新しい配列を設定し、最初に次の k 数値を取得し、次に前の nums.size()-k 数値を取得します。ただし、k>nums.size() の場合、k を処理する必要があることに注意してください。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.size()<k) k=k-k/nums.size()*nums.size();
vector<int> result;
for(int i=nums.size()-k;i<nums.size();i++){
result.push_back(nums[i]);
}
for(int i=0;i<nums.size()-k;i++){
result.push_back(nums[i]);
}
nums=result;
}
};
方法 2: 空間計算量 O(1) のインプレース アルゴリズムを使用する リバースとは、
前に閉じられてから開く間隔であることに注意してください。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.size()<k) k=k-k/nums.size()*nums.size();
reverse(nums.begin(),nums.end());
reverse(nums.begin(),nums.begin()+k);
reverse(nums.begin()+k,nums.end());
}
};
724. 配列の中心インデックスを見つける
1. 1 回トラバースして合計 sum を求めます。
2. 2 回目のトラバースで中央インデックスの左半分と leftSum
判定を求めます。 sum-nums[i]=2*k?
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
int k=0;
for(int i=0;i<nums.size();i++){
if(i==0||(i==nums.size()-1)){
if(sum-nums[i]==0)
return i;
}
else{
k+=nums[i-1];
if(sum-nums[i]==2*k) return i;
}
}
return -1;
}
};
922. 奇数偶数による配列のソート II
方法 1: 余分なスペースは適用されません。
最初の層 while は各要素を走査し、2 番目の層は各要素を走査するのと同等で、時間計算量は O(n^2) です。
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
//使用双指针
int slow=0;
int fast=0;
while(slow<nums.size()){
if(nums[slow]%2==1 && slow%2==0){
//只对这种情况进行交换
fast=0;
while(!(nums[fast]%2==0&&fast%2==1)){
fast++;
if(fast>=nums.size()) break;
}
if(fast>=nums.size()) break;
//交换
int temp=nums[slow];
nums[slow++]=nums[fast];
nums[fast]=temp;
}
slow++;
}
return nums;
}
};
最適化されたライティング: 実際、これは前のアイデアと一致しています。
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
int index=1;//奇数位
for(int i=0;i<nums.size();i+=2){
//遍历偶数位
if(nums[i]%2==1){
while(nums[index]%2!=0) index+=2;
swap(nums[index],nums[i]);
}
}
return nums;
}
};
各 while ループ高速は必ずしも最初から検索を開始するわけではありません。置き換える必要がある以前のものはすでに置き換えられており、再度読み取ると無駄が発生するためです。
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
//使用双指针
int slow=0;
int fast=1;
while(slow<nums.size()){
if(nums[slow]%2==1 ){
//只对这种情况进行交换
while(nums[fast]%2!=0){
fast+=2;
if(fast>=nums.size()) break;
}
if(fast>=nums.size()) break;
//交换
swap(nums[slow],nums[fast]);
}
slow+=2;
}
return nums;
}
};
追跡記録
21 ソートされたリンクリストを結合する
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* virtul_head=new ListNode(0);//虚拟头节点
ListNode* temp=virtul_head;
while(list1&&list2){
//将两个链表中较小的节点加到新链表的下一个
//类似于双指针的思想,list1和list2指向两个链表中还未加入新链表的第一个节点
if(list1->val<list2->val){
temp->next=list1;
list1=list1->next;
}
else{
temp->next=list2;
list2=list2->next;
}
temp=temp->next;//temp指向新链表的最后一个节点
// temp->next=NULL;
}
if(list1!=NULL){
temp->next=list1;
}
if(list2!=NULL){
temp->next=list2;
}
return virtul_head->next;
}
};