最大の数値を比較する問題では、最初の1桁が大きい数値を優先すると考えがちですが、最初の1桁が同じ場合に2つの文字列の大小をどう比較するかがポイントとなります。
非常に直感的なアイデアは、s1 + s2 と s2 + s1 です。この 2 つを比較すると、大きい方が最初になります。証明は少し複雑です。べき乗に従って比較するために文字列を整数に変換することも、反証明方法を試すこともできます。詳細については、ソリューション エリアを参照してください。
注意すべき境界条件は、0が複数あり、最後に0が1つだけ残っている場合で、コードは次のとおりです。
class Solution {
public:
static bool mycmp(const string& s1, const string& s2) //这里要写成静态函数, 成员函数无法传给sort的第三个参数
{
string ss1 = s1 + s2;
string ss2 = s2 + s1;
return ss1 > ss2;
}
string largestNumber(vector<int>& nums) {
vector<string>vs;
if (nums.empty()) return "";
for (int x:nums)
{
vs.push_back(to_string(x));
}
sort(vs.begin(), vs.end(), mycmp);
string ans = "";
ans = accumulate(vs.begin(), vs.end(), ans);
int i=0;
while (i<ans.length() && ans[i]=='0') ++i; // 处理多个0的情况, 其实可以在sort后面判断,第一个数为‘0’表示全为0
ans = string(ans.begin()+i, ans.end());
if (ans.empty()) ans = "0";
return ans;
}
};
直線上の点の最大数を求めるというのは非常に斬新な問題ですが、各点と他の点を結ぶ線の傾きを計算し、いくつかの特別な値を用いて同じ点と傾きが存在しない点を区別するという暴力的な方法しか思いつきません。
もう一つ注意すべき点は、傾きを double で表現する場合の精度の問題です。これまでに見た解決策は、1. 結果に大きな数を掛ける、2. 分数で割って表現する、2 番目の方法は、分子または分母に負の数がある場合を解決する必要があります。
nlognには解決策があるそうなので今後ゆっくり勉強してみます
コードは以下のように表示されます。
class Solution {
public:
int maxPoints(vector<vector<int>>& points) {
int size = points.size();
int ans = 0;
for (int i=0; i<size; ++i)
{
unordered_map<double, int>mmap;
int cnt = 0;
for (int j=i; j<size; ++j)
{
double temp;
auto p1 = points[i];
auto p2 = points[j];
if(p1==p2)
{
temp = INT_MIN; //用 INT_MIN来表示起始点相同的点
}
else
{
temp = (p1[0]==p2[0])? INT_MAX: 1000000.0*(p2[1]-p1[1])/(p2[0]-p1[0]); // 处理精度问题
}
mmap[temp]++;
}
for (auto &pr: mmap)
{
if(pr.first != INT_MIN) cnt = max(cnt, pr.second);
}
ans = max(ans, cnt+mmap[INT_MIN]);
}
return ans;
}
};
高さに応じて再キューイングする場合、最初に頭に浮かぶのは、低いものから高いものへ配置し、次に空のキューを低いものから高いものへ埋めていくことです。同じ高さの順序に注意を払い、同じアイデアを高いものから低いものへ
コードは以下のように表示されます。
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
int size = people.size();
vector<vector<int>>ans(size, vector<int>());
sort(people.begin(), people.end()); // 先比较身高, 再比较序号
int preheight = -1;
for (int i=0; i<size; ++i)
{
auto person = people[i];
int step = person[1];
int tempheight = person[0];
for (int j=0; j<size; ++j)
{
if (step==0 && ans[j].empty())
{
ans[j] = person;
break;
}
if ((ans[j].empty() || ans[j][0]==tempheight) && step>0 ) --step;
}
}
return ans;
}
};
雨水が溜まるという古典的な問題では、「溝」で状況が判断できれば、単調スタックの使用を考えることができます。溝の両側の柱のうち、下側の柱によって受けられる雨水が決まります。半分が上がる、半分が下がることに注意してください。
また、ダブルポインタ法もありますが、正直、面接中に一度に考えるのは非常に難しいので、単調な積み重ねを正直に実践してください。
また、雨水を捉えるための進化した3Dバージョンもあり、全く発想が異なるので、2Dバージョンに気を取られることはありません。
class Solution {
public:
int count_water(vector<int>&height, int start ,int end)
{
int ret = 0;
int target = min(height[start], height[end]);
for (int i=start+1; i<end; ++i)
{
ret += target - height[i];
}
return ret;
}
int trap(vector<int>& height) {
int size = height.size();
if (size==0) return 0;
int premax = height[0], preidx = 0; // 利用premax省去了建单调栈, 空间复杂度降为常数
int ans = 0;
for (int i=1; i<size; ++i)
{
if (height[i]>=premax)
{
ans += count_water(height, preidx, i);
premax = height[i];
preidx = i;
}
}
int maxidx = preidx;
premax = height[size-1], preidx = size-1;
for (int i=size-1; i>=maxidx; --i)
{
if (height[i]>=premax)
{
ans += count_water(height, i, preidx);
premax = height[i];
preidx = i;
}
}
return ans;
}
};
スカイライン問題、この種の問題はこれまで見たことがありません。これを行うには長い経験があり、重要なアイデアは、
1. 毎回、現在の横座標の最も高い縦座標を転換点として移動します。
2. 建物の右端点に遭遇した場合、最高点を判断する前に高さを削除する必要があります
3. 同じ水平座標を持つ複数の建物がある場合、最高点を判断する前に、すべての垂直座標を開始点か終了点に応じて高さ配列で処理する必要があります。
高さの配列にはマルチセットを使用することを推奨します +height と -height で左端点か右端点かを判断するために、端点配列を保持することもできます
class Solution {
public:
vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
multiset<int>heights; // 存放高度
vector<pair<int,int>>points; // 存放当前遍历到的所有点
vector<vector<int>>ans;
for (auto& build: buildings)
{
int start = build[0], end = build[1], height = build[2];
points.push_back(make_pair(start, -height)); // 负数表示起始点, 正数表示终结点
points.push_back(make_pair(end, height));
}
sort(points.begin(), points.end());
int preheight = -1;
for (int i=0; i<points.size(); ++i)
{
auto point = move(points[i]);
int idx = point.first, yidx = point.second;
if(yidx<0) heights.insert(-yidx); // 起始点, 加入高度
else heights.erase(heights.find(yidx)); // 终结点, 移除高度, 这里不能根据值erase, 否则会把所有的值全部删除
if(i+1 < points.size() && points[i+1].first==idx) continue; // 下一个点横坐标相同, 要一起处理
int tempheight = heights.empty()? 0 : *(heights.rbegin());
if (tempheight!=preheight)
{
vector<int>temp = {idx, tempheight};
ans.push_back(temp);
preheight = tempheight;
}
}
return ans;
}
};
これは描画の問題に少し似ています。実際、単調スタックを使用する場合、どのような状況で最大の領域を取得できるかを考える必要があります。直感的には、列が取得できる最大の領域は、その高さよりも小さい列が表示されるまで左右にスキャンすることです。高さ * 幅を使用します。
単調スタックは、実際には、右側の境界を素早く見つけるという問題を解決します。同時に、その単調な特性により、左側の境界もすぐに見つけることができます。注意すべき唯一のことは、スタックが空の場合、前方の高い柱は無視されることです。
ユースケース [5 , 4 , 1 , 2] では、前の 5 を注意せずにスタックから飛び出した場合、列 4 で取得できる領域は 4*2 ではなく 4*1 としか数えられず、間違った解が得られます。
より独創的な方法は、番兵を使用し、番兵を前に配置し、高さがすべての列よりも低いことを確認する (つまり、スタックから飛び出さないようにする) ことにより、正しい左側の境界が確実に見つかるようにすることです。
センチネルを右側に配置する利点は、元の配列内のすべてのデータがポップアウトされ、すべての高さが計算されていることを確認できるため、欠落した解が存在しないことです。
コードは以下のように表示されます
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int>acending;
heights.insert(heights.begin(), 0); // 可以在原数组中插入1, 也可以在栈中放一个下标为-1的虚拟值
heights.push_back(0);
int size = heights.size();
int ans = -1;
int minele;
for(int i=0; i<size; ++i)
{
if (acending.empty())
{
acending.push(i);
ans = max(ans, heights[i]);
}
else
{
if(heights[i] >= heights[acending.top()])
{
acending.push(i);
}
else
{
while(heights[acending.top()]>heights[i])
{
int idx = acending.top();
acending.pop();
int startidx = acending.top()+1;
int temp = heights[idx] * (i-startidx);
ans = max(ans, temp);
}
acending.push(i);
}
}
}
// 下面的代码由于哨兵的加入可以省略,可见哨兵的作用杠杠滴
// while (!acending.empty())
// {
// int idx = acending.top();
// acending.pop();
// if(acending.empty())
// {
// minele = heights[idx];
// break;
// }
// int temp = heights[idx] * (size-acending.top()-1);
// ans = max(ans, temp);
// }
// ans = max(ans, minele * size);
return ans;
}
};