leetcode刷题记录
1、两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
vector与数组无区别
方法一:暴力遍历
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int i=0;
int j=0;
for( i=0;i<nums.size();i++)`
{
for( j=i+1;j<nums.size();j++)`
{
if(nums[i]+nums[j]==target)`
{
return {i,j};`
}
}
}
return {};
}
};
方法二:哈希表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for (int i = 0; i < nums.size(); ++i) {
auto it = hashtable.find(target - nums[i]);
if (it != hashtable.end()) {
return {it->second, i};
}
hashtable[nums[i]] = i;
}
return {};
}
};
auto可以自动转变类型;
unordered_map无序映射是存储键值和映射值组合形成的元素的关联容器,它允许根据键快速检索单个元素。
2、两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
/**
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
int len1=1; //l1和l2的长度
int len2=1;
ListNode* p=l1;
ListNode* q=l2;
while(p->next!=NULL)
{
len1++;
p=p->next;
}
while(q->next!=NULL)
{
len2++;
q=q->next;
}
if(len1>len2)
{
for(int i=0;i<len1-len2;i++)
{
q->next=new ListNode(0);
q=q->next;
}
}else{
for(int i=0;i<len2-len1;i++)
{
p->next=new ListNode(0);
p=p->next;
}
}
p=l1;
q=l2;
bool count=false; //进位
int i=0;
ListNode* l3=new ListNode(-1);
ListNode* w=l3;
while(p!=NULL&&q!=NULL)
{
i=count+p->val+q->val;
w->next=new ListNode(i%10);
count=i>=10?true:false;
p=p->next;
q=q->next;
w=w->next;
}
if(count)
{
w->next=new ListNode(1);
w=w->next;
}
return l3->next;
}
};
ListNode是包含当前值和地址,所以使用前应判断指向是否为空
简化后:
/**
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* l3=new ListNode(-1);//用来存储结果
ListNode* w=l3;
bool count=false;
int sum=0;
while(l1!=NULL || l2!=NULL)
{
sum=0;
if(l1!=NULL)
{
sum+=l1->val;
l1=l1->next;
}
if(l2!=NULL)
{
sum+=l2->val;
l2=l2->next;
}
sum+=count;
w->next=new ListNode(sum%10);
w=w->next;
count=sum>=10?true:false;
}
if(count)
{
w->next=new ListNode(1);
}
return l3->next;
}
};
3、无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
方法一:滑动窗口
其中再次涉及到哈希表
unordered_map介绍(单向迭代器)
count(),返回的是被查找元素的个数。如果有,返回1;否则,返回0
erase()删除一个元素
find()返回被查找元素的位置,没有则返回map.end()。
map与unordered_map相比:
map底层实现为红黑数,undered_map底层实现为哈希表,两者均不能有重复的建,均支持[]运算符
map与multimap相比:
两者底层实现均为红黑树,但是multimap支持重复的键,不支持[]运算符
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len=0;
unordered_map<char, int> hashtable;
for(int i=0;i<s.size();i++)
{
for(int j=i;j<s.size();j++)
{
auto it=hashtable.find(s[j]);
if(it!=hashtable.end())
{
break;
}
hashtable[s[j]]=j;
}
if(hashtable.size()>len)
{
len=hashtable.size();
}
hashtable.clear();
}
return len;
}
};
unordered_set介绍(单向迭代器)
unordered_set::insert插入元素
unordered_set::find寻找元素位置
unordered_set::erase删除具体位置的元素unordered_set::count(e)判断内部是否有e,返回1/0
简化后:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 哈希集合,记录每个字符是否出现过
unordered_set<char> occ;
int n = s.size();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
// 枚举左指针的位置,初始值隐性地表示为 -1
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.erase(s[i - 1]);
}
while (rk + 1 < n && !occ.count(s[rk + 1])) {
// 不断地移动右指针
occ.insert(s[rk + 1]);
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1);
}
return ans;
}
};
4、寻找两个正序数组的中位数
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
输入:nums1 = [], nums2 = [1]
输出:1.00000
方法一:合并数组,直接取中位数(O(m+n))
vector介绍
sort(v.begin(),v.end());排序
push_back()/pop_back();插入/删除(尾部)
size()元素的个数
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len1=nums1.size();
int len2=nums2.size();
if(len1==0)
{
if(len2%2==0)
{
return (nums2[len2/2]+nums2[len2/2-1])/2.0;
}
else
{
return nums2[len2/2];
}
}
if(len2==0)
{
if(len1%2==0)
{
return (nums1[len1/2]+nums1[len1/2-1])/2.0;
}
else
{
return nums1[len1/2];
}
}
for(int i=0;i<len2;i++)
{
nums1.push_back(nums2[i]);
}
sort(nums1.begin(),nums1.end());
int len=nums1.size();
if(len%2==0)
{
return (nums1[len/2]+nums1[len/2-1])/2.0;
}
else{
return nums1[len/2];
}
}
};
方法二:二分法
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) //保证数组1一定最短 //为了加快速度 对长度短的进行二分
{
return findMedianSortedArrays(nums2, nums1);
}
int LMax1 = 0, LMax2 = 0, RMin1 = 0, RMin2 = 0, c1, c2, lo = 0, hi = n;
while (lo <= hi) {
c1 = (hi + lo + 1) / 2;
c2 = (m + n) / 2 - c1;
LMax1 = (c1 == 0) ? INT_MIN : nums1[c1 - 1];
RMin1 = (c1 == n) ? INT_MAX : nums1[c1];
LMax2 = (c2 == 0) ? INT_MIN : nums2[c2 - 1];
RMin2 = (c2 == m) ? INT_MAX : nums2[c2];
if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
if ((m + n) % 2)
return min(RMin1, RMin2);
else
return ((int64_t)max(LMax1, LMax2) + (int64_t)min(RMin1, RMin2)) / 2.0;
}
};
349、两个数的交集
给定两个数组,编写一个函数来计算它们的交集。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
方法一:两轮循环,依次比较,借用哈希表,从O(m*n)下降至O(m+n)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> set1,set2;
for(auto num:nums1) //顺序访问
{
set1.insert(num);
}
for(auto num:nums2)
{
set2.insert(num);
}
return intersectionSet(set1,set2);
}
vector<int> intersectionSet(unordered_set<int> set1,unordered_set<int> set2){
if(set1.size()>set2.size())
{
return intersectionSet(set2,set1);
}
vector<int> result;
for(auto num:set1)
{
if(set2.count(num))
{
result.push_back(num);
}
}
return result;
}
};
19、删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
方法一:我们可以设想假设设定了双指针 p 和 q 的话,当 q 指向末尾的 NULL,p 与 q 之间相隔的元素个数为 n 时,那么删除掉 p 的下一个指针就完成了要求。
/**
* 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* removeNthFromEnd(ListNode* head, int n) {
ListNode *empty= new ListNode(0);
empty->next=head;
ListNode *a=empty;
ListNode *b=empty;
for(int i=0;i<n+1;i++)
{
b=b->next;
}
while(b)
{
a=a->next;
b=b->next;
}
ListNode *delNode=a->next;
a->next=a->next->next;
delete delNode;
ListNode *ret=empty->next;
delete empty;
return ret;
// return empty->next;
// empty=empty->next;
// return empty;
941、有效的山脉数组
给定一个整数数组 A
,如果它是有效的山脉数组就返回 true
,否则返回 false
。
让我们回顾一下,如果 A 满足下述条件,那么它是一个山脉数组:
给定一个整数数组A,如果它是有效的山脉数组就返回true,否则返回false。
让我们回顾一下,如果 A 满足下述条件,那么它是一个山脉数组:
1\A.length >= 3
2\在 0 < i < A.length - 1 条件下,存在 i 使得:
A[0] < A[1] < ... A[i-1] < A[i]
A[i] > A[i+1] > ... > A[A.length - 1]
方法一:双指针法
class Solution {
public:
bool validMountainArray(vector<int>& A) {
if(A.size()<3)
{
return false;
}
int i=0,j=A.size()-1;
while(i<A.size()-1 && A[i]<A[i+1])
{
++i;
}
while(j>0&&A[j-1]>A[j])
{
j--;
}
if(i==0||j==A.size()-1)
{
return false;
}
return (i==j)?true:false;
}
};
11、盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
方法一:暴力枚举
class Solution {
public:
int maxArea(vector<int>& height) {
if(height.size()<2)
{
return 0;
}
int Max=0;
for(int i=0;i<height.size()-1;i++)
{
for(int j=i;j<height.size();j++)
{
Max=Max<min(height[i],height[j])*(j-i)?min(height[i],height[j])*(j-i):Max;
}
}
return Max;
}
};
方法二:双指针
class Solution {
public:
int maxArea(vector<int>& height) {
if(height.size()<2)
{
return 0;
}
int Max=0,i=0,j=height.size()-1;
while(i<j)
{
Max=Max<min(height[i],height[j])*(j-i)?min(height[i],height[j])*(j-i):Max;
if(height[i]<=height[j])
{
i++;
}else{
j--;
}
}
return Max;
}
};
16、最近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
方法一:利用排序+双指针,时间复杂度降低至O(n^2)
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int num2=10000; //记录每次的差值
int num=0; //记录结果
if(nums.size()<3)
{
return 0;
}
sort(nums.begin(),nums.end());
int i=nums.size()-3;
while(i>=0)
{
int j=i+1,k=nums.size()-1;
while(j<k)
{
num=abs(nums[i]+nums[j]+nums[k]-target)<num2?nums[i]+nums[j]+nums[k]:num;
num2 = abs(nums[i] + nums[j] + nums[k] - target)<num2? abs(nums[i] + nums[j] + nums[k] - target):num2;
if(nums[i]+nums[j]+nums[k]-target==0)
{
return target;
}else if(nums[i]+nums[j]+nums[k]-target>0)
{
k--;
}else
{
j++;
}
}
i--;
}
return num;
}
};
18、四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
**注意:**答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
方法一:参考之前的四数之和
class Solution {
public:
std::vector<std::vector<int>> fourSum(std::vector<int>& nums, int target) {
std::vector<std::vector<int>> result;
if (nums.size() < 4)
{
return result;
}
sort(nums.begin(), nums.end());
int i = nums.size() - 4;
while (i >= 0)
{
while (i > 0 && nums[i] == nums[i - 1])
{
i--;
}
int j = nums.size() - 3;
while (j > i)
{
while (j > i+1 && nums[j] == nums[j - 1])
{
j--;
}
int k = j + 1, m = nums.size() - 1;
while (k < m)
{
if (nums[i] + nums[j] + nums[k] + nums[m] == target)
{
result.push_back({ nums[i],nums[j],nums[k],nums[m] });
k++;
m--;
while (k < m && nums[k] == nums[k - 1])
{
k++;
}
while (k < m && nums[m] == nums[m + 1])
{
m--;
}
}
else if (nums[i] + nums[j] + nums[k] + nums[m] > target)
{
m--;
}
else {
k++;
}
}
j--;
}
i--;
}
return result;
}
};
36、有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 '.'
表示。
示例:
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
方法一:依次访问,用三个数组代表行、列、方块((i / 3) * 3 + j / 3)。这里全初始化为0,用减“1”来给对应位置赋值1,如果有不是0的就为假、
class Solution {
public:
bool isValidSudoku(std::vector<std::vector<char>>& board) {
std::vector<std::vector<int>> row(9,std::vector<int>(9,0));
std::vector<std::vector<int>> col(9, std::vector<int>(9, 0));
std::vector<std::vector<int>> boxes(9, std::vector<int>(9, 0));
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
if (board[i][j] == '.')
{
continue;
}
int index= (i / 3) * 3 + j / 3;
int val = board[i][j] - '1';
if (row[i][val] == 0 && col[j][val]==0 && boxes[index][val] == 0)
{
row[i][val] = 1;
col[j][val] = 1;
boxes[index][val] = 1;
}
else {
return false;
}
}
}
return true;
}
};
30、串联所有单词的子串
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
//示例1:
//输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
//输出:[0,9]
//解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
方法一:
利用两个hash表,表一用来存储数组的字符串;
表二用来存储不同位置的字符串;
比较两个hash表是否相同。
注意点:
1、unordered_map.substr(start,size)后面的是尺寸
2、unordered_map判断相同的时候,顺序不同也不算相同
3、把比较相同转化为内部的变量比较
class Solution {
public:
std::vector<int> findSubstring(std::string s, std::vector<std::string>& words) {
std::vector<int> result;
int wordNum = words.size();
if (wordNum == 0)
{
return result;
}
int wordLen = words[0].size();
std::unordered_map<std::string, int> Num1;
for (int i = 0; i < wordNum; i++)
{
Num1[words[i]]++;
}
//for (const auto &pair : Num1) {
// std::cout << pair.first << ": " << pair.second << '\n';
//}
std::unordered_map<std::string, int> Num2;
for (int i = 0; i < s.size() - wordLen * wordNum + 1; i++)
{
//std::cout << i << std::endl;
int num = 0;
while (num<wordNum)
{
//std::cout << num << std::endl;
std::string ret = s.substr(i + num*wordLen, wordLen);
//std::cout << i + num * wordLen<< i + (num + 1) * wordLen << ret << std::endl;
//std::cout << "0" << ret << std::endl;
if (Num1[ret]!=0)
{
//std::cout << "1"<<ret << std::endl;
Num2[ret]++;
if (Num2[ret] > Num1[ret])
{
break;
}
//std::cout << "2" << ret << std::endl;
}
else
{
break;
}
if (num == wordNum-1)
{
result.push_back(i);
}
num++;
}
Num2.clear();
}
return result;
}
};
49、字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
方法一:
哈希表:利用每个字符串的ASCII值,乘积+和可以确定一个字符串的小写字母;并以此为key值。对应的不同排序的字符串为value值。
注意:在涉及到大规模运算的时候,要防止溢出,所以此处用unsigned int。
class Solution {
public:
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
std::vector<std::vector<std::string>> result;
if (strs.size() == 0)
{
return result;
}
std::unordered_map<unsigned int, std::vector<std::string>> map1;
for (int i = 0; i < strs.size(); i++)
{
// 设置unsigned int 防止溢出
unsigned int num1 = 0;
unsigned int num2 = 1;
for (int j = 0; j < strs[i].size(); j++)
{
num1 += strs[i][j];
num2 *= strs[i][j];
}
map1[num1+num2].push_back(strs[i]);
}
for (const auto &pair:map1)
{
result.push_back(pair.second);
}
return result;
}
};
452、用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。
//实例一;
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
方法一:排序+贪心算法
//注意lambda的使用
sort(points.begin(), points.end(), [](const std::vector<int>& a, const std::vector<int>& b) {
return a[1] < b[1];
});
class Solution {
public:
int findMinArrowShots(std::vector<std::vector<int>>& points) {
if (points.size() == 0)
{
return 0;
}
sort(points.begin(), points.end(), [](const std::vector<int>& a, const std::vector<int>& b) {
return a[1] < b[1];
});
int pos = points[0][1];
int num = 1;
for (const auto &it : points)
{
if (it[0] > pos)
{
pos = it[1];
++num;
}
}
return num;
}
};
双指针
42、接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
方法一:
抓住关键思想:每个位置的水量 等于 两侧的最大边中较小的边减去当前位置的值。
class Solution {
public:
int trap(std::vector<int>& height) {
if (height.size() <= 1)
{
return 0;
}
int num = 0;
for (int i = 1; i < height.size() - 1; ++i)
{
num += IMax(height, i);
}
return num;
}
int IMax(std::vector<int>& height,int k)
{
if (k<1 || k>height.size() - 1)
{
return 0;
}
int leftMax = 0;
int rightMax =0;
for (int i = 0; i < k; ++i)
{
leftMax = std::max(leftMax,height[i]);
}
for (int j = height.size() - 1; j > k; --j)
{
rightMax = std::max(rightMax, height[j]);
}
return std::min(leftMax, rightMax)>height[k]? std::min(leftMax, rightMax) - height[k]:0;
}
};
方法二:
对上一个方法进行改进,上一个方法是O(n^2),这个是O(n);利用动态规划的思想,把n次遍历找最值的问题转化为提前存储好对应的最大值。
class Solution {
public:
int trap(std::vector<int>& height) {
if (height.size() <= 1)
{
return 0;
}
int left = 0;
int right = 0;
std::vector<int> leftMax;
std::vector<int> rightMax(height.size(),0);
for (int i = 0; i < height.size(); ++i)
{
left = std::max(left, height[i]);
leftMax.push_back(left);
}
for (int i = height.size() - 1; i > -1; --i)
{
right = std::max(right, height[i]);
rightMax[i] = right;
}
int num = 0;
for (int i = 1; i < height.size()-1; ++i)
{
if (std::min(leftMax[i], rightMax[i]) > height[i])
{
num += std::min(leftMax[i], rightMax[i]) - height[i];
}
}
return num;
}
};
方法三:双指针
class Solution {
public:
int trap(std::vector<int>& height) {
if (height.size() <= 1)
{
return 0;
}
int num = 0;
int leftMax = 0;
int rightMax = 0;
int left = 0;
int right = height.size()-1;
while (left<right)
{
if (height[left] < height[right])
{
leftMax = std::max(leftMax, height[left]);
num += leftMax - height[left];
++left;
}
else
{
rightMax = std::max(rightMax, height[right]);
num += rightMax - height[right];
--right;
}
}
return num;
}
};
61、旋转列表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数
示例一:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
方法一:先把链表首尾连接;
记录链表的长度;
利用规律是从len-k%len的位置开始,即表头位置;
最后再断开表头和表尾。
/**
* 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* rotateRight(ListNode* head, int k) {
if (!head)
{
return nullptr;
}
ListNode* end = head;
int len = 1;
while (end->next)
{
end = end->next;
++len;
}
end->next = head;
for (int i = 0; i <= len - k % len; ++i)
{
end = end->next;
}
ListNode* start = end;
for (int i = 0; i < len - 1; ++i)
{
end = end->next;
}
end->next = nullptr;
return start;
}
};
递归,深度优先搜索
98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
方法一:递归
注意:需要左右两边都是真才是真,不能只考虑单独一边。体现在最后一个return上。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode* root) {
return helper(root,LONG_MIN,LONG_MAX);
}
bool helper(TreeNode* root,long long downer,long long upper)
{
if(!root)
{
return true;
}
if( root->val<=downer ||root->val >= upper)
{
return false;
}else
{
return helper(root->left,downer,root->val) && helper(root->right,root->val,upper);
}
}
};
方法二:中序遍历(不太熟悉)
按中序遍历的顺序是升序的,如果不满足就不是。所以与前一个元素进行比较大小。
class Solution {
public:
bool isValidBST(TreeNode* root) {
long long upper = (long long)INT_MIN - 1;
std::stack <TreeNode*> ret;
while(!ret.empty() || root)
{
while(root)
{
ret.push(root);
root=root->left;
}
//此循环后root指向空
root=ret.top();
std::cout<<root->val<<std::endl;
ret.pop();
if(root->val<=upper)
{
return false;
}
upper=root->val;
root=root->right;
}
return true;
}
};
100、相同的树
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
输入: 1 1
/ \ / \
2 3 2 3
[1,2,3], [1,2,3]
输出: true
输入: 1 1
/ \
2 2
[1,2], [1,null,2]
输出: false
方法一:递归实现,有点参考98题的第一种方法。必须左右两边是真才真,这也是递归需要注意的问题。利用空指针的条件判断为真。
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(!p&&!q)
{
return true;
}else if(!p&&q)
{
return false;
}else if(p&&!q)
{
return false;
}else
{
if(p->val==q->val)
{
return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}else
{
return false;
}
}
}
};
101、对称二叉树
给定一个二叉树,检查它是否是镜像对称的 。
例如,二叉树 [1,2,2,3,4,4,3]
是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3]
则不是镜像对称的:
1
/ \
2 2
\ \
3 3
方法一:递归。把镜像问题转化为判断两个子树是否对称。在100题中,判断相同的树可以借鉴。只需要思考:相同的树是判断对应的左右子树,即左子树和左子树对比,右子树和右子树对比;此题中考虑镜像,可以用左子树和右子树对比,右子树和左子树对比。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(!root)
{
return true;
}else
{
return isSame(root->left,root->right);
}
}
bool isSame(TreeNode* root1,TreeNode * root2)
{
if(!root1&&!root2)
{
return true;
}else if(!root1&&root2)
{
return false;
}else if(root1&&!root2)
{
return false;
}else
{
if(root1->val==root2->val)
{
return isSame(root1->left,root2->right)&&isSame(root1->right,root2->left);
}else
{
return false;
}
}
}
};
方法二:迭代。思想大致是一样的,这里用到队列,把可能相同的元素放在一起存入。
**首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。**初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(!root)
{
return true;
}else
{
return isSame(root->left,root->right);
}
}
bool isSame(TreeNode* root1,TreeNode * root2)
{
std::queue<TreeNode*> ret;
ret.push(root1);
ret.push(root2);
while(!ret.empty())
{
root1=ret.front();
ret.pop();
root2=ret.front();
ret.pop();
if(!root1&&!root2)
{
continue;
}
if((!root1||!root2)||(root1->val!=root2->val))
{
return false;
}
ret.push(root1->left);
ret.push(root2->right);
ret.push(root1->right);
ret.push(root2->left);
}
return true;
}
};
104、二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度是3。
方法一:递归 :max(l,r)+1
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)
{
return 0;
}
return std::max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
方法二:广度优先搜索
每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展,这样能保证每次拓展完的时候队列里存放的是当前层的所有节点,即我们是一层一层地进行拓展,最后我们用一个变量 ans 来维护拓展的次数,该二叉树的最大深度即为ans。
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root) return 0;
std::queue<TreeNode*> ret;
int ans=0;
ret.push(root);
while(!ret.empty())
{
int ms=ret.size();
while(ms>0)
{
root=ret.front();
ret.pop();
if(root->left) ret.push(root->left);
if(root->right) ret.push(root->right);
--ms;
}
++ans;
}
return ans;
}
};
105、从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
方法一:递归。想到利用前序第一个点是树根。
注意:第一:用到哈希表的方法在中序排序中找到树根;
第二:用位置代替多次的拷贝数组。就在原数组上进行操作,只是新的数组用位置表示。
注意:涉及到后序排序的前半段的末尾点和后半段的起始点,需要用preorder的初始点或者末尾点外加左子树的大小或者右子树的大小来计算,不可以直接想当然用topId+1或者topId-1来代替
class Solution {
public:
std::unordered_map<int, int> ret;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for (int i = 0; i < inorder.size(); ++i)
{
ret[inorder[i]] = i;
}
for (const auto& iter : ret)
{
std::cout << iter.first << iter.second << std::endl;
}
return makeTree(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1);
}
TreeNode* makeTree(const vector<int>& preorder,const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right)
{
if (preorder_left > preorder_right)
{
return nullptr;
}
int preorder_top = preorder_left;
int inorder_top = ret[preorder[preorder_top]];
TreeNode* root = new TreeNode(preorder[preorder_top]);
int size = inorder_top - inorder_left;
root->left = makeTree(preorder, inorder, preorder_left + 1, preorder_left+size, inorder_left, inorder_top - 1);
root->right= makeTree(preorder, inorder, preorder_left+size+1, preorder_right, inorder_top+1, inorder_right);
return root;
}
};
106. 从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
方法类似于105题目,但是注意顶点的取值:涉及到后序排序的前半段的末尾点和后半段的起始点,需要用postorder的初始点或者末尾点外加左子树的大小或者右子树的大小来计算,不可以直接想当然用topId+1或者topId-1来代替
class Solution {
std::unordered_map<int, int> inHash;
public:
TreeNode* buildTree(std::vector<int>& inorder, std::vector<int>& postorder) {
int len = inorder.size();
if (len == 0)
{
return nullptr;
}
for (int i=0;i<len;++i)
{
inHash[inorder[i]] = i;
}
return makeTree(inorder, postorder, 0, len - 1, 0, len - 1);
}
TreeNode* makeTree(const std::vector<int>& inorder, const std::vector<int>& postorder, int inLeft, int inRight, int postLeft, int postRight)
{
if (postLeft > postRight)
{
return nullptr;
}
TreeNode* root =new TreeNode(postorder[postRight]);
int topId = inHash[postorder[postRight]];
int len2 = inRight - topId;
root->left = makeTree(inorder, postorder, inLeft, topId-1, postLeft, postRight-len2- 1);
root->right = makeTree(inorder, postorder, topId + 1, inRight, postRight-len2, postRight-1);
//涉及到后序排序的前半段的节点和后半段的起始点,需要用初始或者末尾点加上左子树的大小或者右子树的大小来计算,不可以直接想当然用topId+1或者topId-1来代替
return root;
}
};
17、电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
方法一:(回溯法)穷举法(递归+DFS)
注意三处注释的部分
class Solution {
public:
std::vector<std::string> letterCombinations(std::string digits) {
std::vector<std::string> combinations;
if (digits.empty())
{
return combinations;
}
std::unordered_map<char, std::string> phoneMap;
phoneMap['2'] = "abc";
phoneMap['3'] = "def";
phoneMap['4'] = "ghi";
phoneMap['5'] = "jkl";
phoneMap['6'] = "mno";
phoneMap['7'] = "pqrs";
phoneMap['8'] = "tuv";
phoneMap['9'] = "wxyz";
std::string combination;
trackBack(combinations, phoneMap, 0, digits, combination);
return combinations;
}
//如果此处用std::vector<std::string> & combinations的&。那么改变只是拷贝值,和原来的值无关。
void trackBack(std::vector<std::string>& combinations, const std::unordered_map<char, std::string> &phoneMap, int index, const std::string digits, std::string combination)
{
//size()和length()功能和代码完全一样
if (index == digits.size())
{
combinations.push_back(combination);
}
else
{
char digit = digits[index];
//当Map容器使用const修饰,不可以用operator[]访问键值元素。因为[]是可以改变map的非const操作
std::string letter = phoneMap.at(digit);
for (const auto& iter : letter)
{
combination.push_back(iter);
trackBack(combinations, phoneMap, index + 1, digits, combination);
combination.pop_back();
}
}
}
};
108. 将有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
方法一:分治
class Solution {
public:
TreeNode* sortedArrayToBST(std::vector<int>& nums) {
return makeTree(nums, 0, nums.size() - 1);
}
TreeNode* makeTree(std::vector<int>& nums, int left, int right)
{
if (left>right)
{
return nullptr;
}
int mid = (left+right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = makeTree(nums, left, mid-1);
root->right = makeTree(nums, mid + 1, right);
return root;
}
};
109、有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
方法一:分治+递归
注意:取链表中位数的方法
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
ListNode* end = head;
while (end)
{
end = end->next;
}
return makeTree(head, head, end);
}
TreeNode* makeTree(ListNode* head, ListNode* left, ListNode* right)
{
if (left == right)
{
return nullptr;
}
ListNode* mid = midNode(head,left,right);
TreeNode* root = new TreeNode(midNode(head,left,right)->val);
root->left = makeTree(head, left, mid);
root->right = makeTree(head, mid->next, right);
return root;
}
ListNode* midNode(ListNode* head,ListNode* left, ListNode* right)
{
ListNode* slow = left;
ListNode* fast = left;
while (fast!=right&&fast->next!=right)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
110、平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:true
方法一:自顶而下
改善方面:每个节点的长度计算需要重复计算其左右子节点的长度
class Solution {
public:
bool isBalanced(TreeNode* root) {
if (!root)
{
return true;
}
return abs(getLength(root->left) - getLength(root->right)) < 2 && isBalanced(root->left) && isBalanced(root->right);
}
int getLength(TreeNode* root)
{
if (!root)
{
return 0;
}
return std::max(getLength(root->left), getLength(root->right)) + 1;
}
};
方法二:自底而上
避免了重复计算
class Solution {
public:
bool isBalanced(TreeNode* root) {
return getLength(root) != -1;
}
int getLength(TreeNode* root)
{
if (!root)
{
return 0;
}
int left = getLength(root->left);
if (left == -1) return -1;
int right = getLength(root->right);
if (right == -1) return -1;
return (abs(left - right) < 2) ? std::max(left,right) + 1 : -1;
}
};
111、二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
示例一:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例二:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
方法一:从左右子树中找更小值进行递归
易犯错误:忘记考虑节点只有一个孩子的情况,误把其当作叶子节点。
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root)
{
return 0;
}
if (!root->left && !root->right)
{
return 1;
}
int mid = INT_MAX;
//两个if()判断用来应对节点只有左孩子和右孩子的情况
if (root->left)
{
mid = std::min(mid, minDepth(root->left));
}
if (root->right)
{
mid = std::min(mid, minDepth(root->right));
}
return mid+1;
}
};
方法二:广度优先搜索
第一次访问到的叶节点就是对应的最小深度,这里利用队列存储pair<节点,深度>
class Solution {
public:
int minDepth(TreeNode *root) {
if (root == nullptr) {
return 0;
}
queue<pair<TreeNode *, int> > que;
que.emplace(root, 1);
while (!que.empty()) {
TreeNode *node = que.front().first;
int depth = que.front().second;
que.pop();
if (node->left == nullptr && node->right == nullptr) {
return depth;
}
if (node->left != nullptr) {
que.emplace(node->left, depth + 1);
}
if (node->right != nullptr) {
que.emplace(node->right, depth + 1);
}
}
return 0;
}
};
112、路径总和1
方法一:
递归,深度优先搜索
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if(!root)
{
return false;
}
if (!root->left && !root->right&&root->val == sum)
{
return true;
}
return hasPathSum(root->left, sum - root->val)||hasPathSum(root->right, sum - root->val);
// bool result = false;
// if (root->left)
// {
// result = result || hasPathSum(root->left, sum - root->val);
// }
// if (root->right)
// {
// result = result || hasPathSum(root->right, sum - root->val);
// }
// return result;
}
};
113、路径总和 2
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
方法一:深度优先搜索
注意:有的时候可以借用共享内存
std::vector<std::vector> result;
std::vector ret;
class Solution {
public:
std::vector<std::vector<int>> result;
std::vector<int> ret;
std::vector <std::vector<int >> pathSum(TreeNode* root, int sum) {
dfs(root, sum);
return result;
}
void dfs(TreeNode* root, int sum)
{
if (!root)
{
return ;
}
ret.push_back(root->val);
if (!root->left && !root->right && root->val == sum)
{
result.push_back(ret);
}
dfs(root->left, sum - root->val);
dfs(root->right, sum - root->val);
ret.pop_back();
}
};
字符串
5、最长回文子串
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
示例2:
输入: "cbbd"
输出: "bb"
方法一:动态规划
回文子串,若s[i–>j]是回文子串,则s[i+1–>j-1]肯定是回文子串。因此产生一个关系,P(i,j)=P(i+1,j−1)∧(Si==Sj)。其中P(i,j)是i到j为回文子串的概率。
同时考虑边界情况。长度为1的时候,肯定是回文子串;长度为2的时候,如果s[i]==s[j],那么也是回文子串。
注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。
注意2:这里的长度需要注意处理,因为长度如果正常计算,考虑数字越界的情况。
以长度为最外层迭代变量。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n));
string ans;
for (int l = 0; l < n; ++l) {
for (int i = 0; i + l < n; ++i) {
int j = i + l;
if (l == 0) {
dp[i][j] = 1;
} else if (l == 1) {
dp[i][j] = (s[i] == s[j]);
} else {
dp[i][j] = (s[i] == s[j] && dp[i + 1][j - 1]);
}
if (dp[i][j] && l + 1 > ans.size()) {
ans = s.substr(i, l + 1);
}
}
}
return ans;
}
};
6、Z字型变化
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING"
行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"
。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
示例 2:
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L D R
E O E I I
E C I H N
T S G
方法一:按行取,主要是找寻其中规律
思路
按照与逐行读取 Z 字形图案相同的顺序访问字符串。
算法
按照(2 * numRows - 2)为一个循环
1、在这个循环中,第一个和第numRows个所在的行只有一个;行 0 中的字符位于索引k(2⋅numRows−2) 处;行 numRows−1 中的字符位于索引 k(2⋅numRows−2)+numRows−1 处;
2、其余行是两个元素,行 i中的字符位于索引 k(2⋅numRows−2)+i 以及 (k+1)(2⋅numRows−2)−i 处。
注意:注释的是待优化的部分(思想相同),未注释是已经优化的。
class Solution {
public:
std::string convert(std::string s, int numRows) {
std::string result;
int len = s.size();
if (len == 0 || numRows < 1)
{
return result;
}
if (numRows == 1)
{
return s;
}
//for (int i = 0; i < numRows; ++i)
//{
// for (int j = 0; j < len; ++j)
// {
// if (j %(2 * numRows - 2) == i)
// {
// if(j%(2 * numRows - 2) == 0 || j %(2 * numRows - 2) == numRows-1)
// {
// result.push_back(s[j]);
// }
// else
// {
// result.push_back(s[j]);
// if (j - i + 2 * numRows - 2 - i < len)
// {
// result.push_back(s[j - i + 2 * numRows - 2 - i]);
// }
// }
//
// }
// }
//}
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j + i < len; j += (2 * numRows - 2))
{
result += s[j + i];
if (i != 0 && i != numRows - 1 && (2 * numRows - 2) - i + j < len)
{
result += s[(2 * numRows - 2) - i + j];
}
}
}
return result;
}
};
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
注脚的解释 ↩︎