面试精选题
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
public class Solution {
public int[] TwoSum(int[] nums, int target)
{
Dictionary<int, int> d = new Dictionary<int, int>();
for(int i=0;i<nums.Length;i++)
{
if(d.ContainsKey(target-nums[i]))
{
return new int[] { d[target-nums[i]],i };
}
else
{
d[nums[i]] = i;
}
}
return new int[] { 0, 0 };
}
}
两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode AddTwoNumbers(ListNode l1, ListNode l2) {
int nSum; //列表对应节点的和
ListNode resNode = new ListNode(0); //返回模型
ListNode curNode = resNode; //当前计算模型
int nAdd = 0; //进位
do
{
int n1 = l1 == null ? 0 : l1.val;
int n2 = l2 == null ? 0 : l2.val;
nSum = n1 + n2 + nAdd; //列表对应节点的和
nAdd = nSum / 10; //十位
curNode.next = new ListNode(nSum % 10);
curNode = curNode.next;
l1 = l1.next;
l2 = l2.next;
} while (l1 != null || l2 != null || nAdd != 0);
return resNode.next;//去除初始化的0
}
}
无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
public class Solution {
public int LengthOfLongestSubstring(string s) {
if (s.Length < 2) return s.Length;
var left = 0;
var right = 0;
var maxlen = 0;
var charSet = new HashSet<char>();
while (right < s.Length) {
if (charSet.Contains(s[right]) == false) {
charSet.Add(s[right++]);
maxlen = Math.Max(maxlen, right - left);
} else {
charSet.Remove(s[left++]);
}
}
return maxlen;
}
}
寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
public double FindMedianSortedArrays(int[] A, int[] B) {
//确保A长度小于等于B
if (A.Length > B.Length)
{
int[] temp = A;
A = B;
B = temp;
}
int iStart = 0;
int iEnd = A.Length;
//正常范围内
while (iStart <= iEnd)
{
int i = (iStart + iEnd) / 2;//取中间索引 二分查找法
//在索引i位置将A切为两半,A左半部分长度为i,右半部分为A.length-i;
//在索引j位置将B切为两半,B左半部分长度为j,右半部分长度为B.length-j;
//将A左半部分与B左半部分相加,A右半部分与B右半部分相加 有i+j=A.length-i+B.length-j 或A.length-i+B.length-j+1;
//整合得到j的表达式
int j = (A.Length + B.Length + 1 - 2 * i) / 2;
//找到中位数时存在以下不等式关系
//此时i偏小 继续二分查找并增大i
if (i < iEnd && B[j - 1] > A[i])
{
iStart = i + 1;
}
//i偏大
else if (i > iStart && A[i - 1] > B[j])
{
iEnd = i - 1;
}
//满足条件
else
{
//边界值
int maxLeft = 0;
if (i == 0)
{
maxLeft = B[j - 1];
}
else if (j == 0)
{
maxLeft = A[i - 1];
}
else
{
maxLeft = Math.Max(A[i - 1], B[j - 1]);
}
//奇数情况
if ((A.Length + B.Length) % 2 == 1)
{
return maxLeft;
}
//边界值
int minRight = 0;
if (i == A.Length)
{
minRight = B[j];
}
else if (j == B.Length)
{
minRight = A[i];
}
else
{
minRight = Math.Min(B[j], A[i]);
}
//偶数情况
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
public string LongestPalindrome(string s)
{
string result = "";
int n = s.Length;
int end = 2 * n - 1;
for (int i = 0; i < end; i ++)
{
double mid = i / 2.0;
int p = (int)(Math.Floor(mid));
int q = (int)(Math.Ceiling(mid));
while (p >= 0 && q < n)
{
if (s[p] != s[q]) break;
p--; q++;
}
int len = q - p - 1;
if (len > result.Length)
result = s.Substring(p + 1, len);
}
return result;
}
整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
public class Solution {
public int Reverse(int x) {
int rev = 0;
while (x != 0) {
if (rev < int.MinValue / 10 || rev > int.MaxValue / 10) {
return 0;
}
int digit = x % 10;
x /= 10;
rev = rev * 10 + digit;
}
return rev;
}
}
字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)
public class Solution
{
public int MyAtoi(string s)
{
// 为空直接返回
if(s.Length==0) return 0;
// 正负号
int plus=1;
// 结果
int result=0;
// 去空格
if(s[0]==' ')
{
for(int i=0;i<s.Length;i++)
{
if(s[i]!=' ')
{
s=s[i..s.Length];
break;
}
}
}
// 判断正负号
if(s[0]=='-')
{
plus=-1;
s=s[1..s.Length];
}
else if(s[0]=='+') s=s[1..s.Length];
// 逐位判断
for(int i=0;i<s.Length;i++)
{
// 数字
if(s[i]>='0'&&s[i]<='9')
{
// 判断是否溢出
if(plus==1)
{
if(result>int.MaxValue/10) return int.MaxValue;
if(result==int.MaxValue/10 && s[i]>='7') return int.MaxValue;
}
else
{
if(result>int.MaxValue/10) return int.MinValue;
if(result==int.MaxValue/10 && s[i]>='8') return int.MinValue;
}
// 更新结果
result=result*10+(s[i]-'0');
}
// 非数字,直接返回结果
else return result*plus;
}
// 判断完毕,返回结果
return result*plus;
}
}
正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
'’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
public class Solution {
public bool IsMatch(string s, string p)//m x n 动态规划法 求正则表达式匹配算法 *号 +号
{
int m = s.Length;
int n = p.Length;
bool[,] f = new bool[m + 1, n + 1];//横竖多一个格子
f[0, 0] = true;
for (int i = 0; i <= m; ++i)//i 代表当前行,j代表当前列 s[i-1]代表 s[j-1]
{
for (int j = 1; j <= n; ++j)//逐行计算,从第二列 (基本上如果有一行没有出现真,可以返回假)
{
if (p[j - 1] == '*')//如果当前列是星
{
if (j > 1)
{
f[i, j] = f[i, j - 2];//当前格先按照星左边的左边格匹配hot(代表0个的时候)
if (UnitMatche(s, p, i, j - 1))//如果星前面能和本行字符匹配
{
f[i, j] = f[i, j] || f[i - 1, j];//本格=本格|正上方
}
}
}
else//如果前一个不是星
{
if (UnitMatche(s, p, i, j))//如果本格匹配
{
f[i, j] = f[i - 1, j - 1];//赋值为左上方匹配
}
}
}
}
return f[m, n];//返回最后一格
}
public bool UnitMatche(string s, string p, int i, int j)//只看一个单元格的匹配情况
{
if (i == 0) return false;//最上一行不匹配,只有0,0点
if (p[j - 1] == '.') return true;//任何字符和点匹配成功
return s[i - 1] == p[j - 1];//返回相等匹配
}
}
盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
public class Solution {
public int MaxArea(int[] height) {
int l = 0, r = height.Length - 1, ans = -1, curr = -1;
while (l <= r) {
curr = (r - l) * Math.Min(height[l], height[r]);
ans = Math.Max(curr, ans);
if (height[l] < height[r]) l++;
else r--;
}
return ans;
}
}
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
//执行用时: 116 ms;内存消耗: 24.5 MB
public class Solution {
public string LongestCommonPrefix(string[] strs) {
if (strs.Length==0){
return "";
}
for (int i = 0; i < strs[0].Length; ++i) {
char c = strs[0][i];
for (int j = 1; j < strs.Length; ++j) {
if (i == strs[j].Length || strs[j][i] != c) {
return strs[0].Substring(0, i);
}
}
}
return strs[0];
}
}
三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
public IList<IList<int>> ThreeSum(int[] nums)
{
IList<IList<int>> result = new List<IList<int>>();
int len = nums.Length;
if (len < 3) return result;
Array.Sort(nums);
for (int i = 0; i < len - 2; i++)
{
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重
int left = i + 1;
int right = len - 1;
while (left < right)
{
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0)
{
result.Add(new List<int>() { nums[i], nums[left], nums[right] });
while (left < right && nums[left] == nums[left + 1]) left++; // 去重
while (left < right && nums[right] == nums[right - 1]) right--; // 去重
left++;
right--;
}
else if (sum < 0) left++;
else if (sum > 0) right--;
}
}
return result;
}
删除链表的倒数第 N 个结点
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode preHead = new ListNode();
preHead.next = head;
ListNode pre, end;
pre = preHead;
end = preHead;
for (var i = 0; i < n; i++)
{
end = end.next;
}
while (end.next != null)
{
pre = pre.next;
end = end.next;
}
// Remove
pre.next = pre.next.next;
return preHead.next;
}
有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
public class Solution {
public bool IsValid(string s) {
Stack<char> st = new Stack<char>();
for(int i = 0;i < s.Length;i++)
{
if(s[i] == '(' || s[i] == '{' || s[i] == '[')
{
st.Push(s[i]);//如果是左括号,入栈
}
else if(s[i] == ')')
{
if(st.Count != 0 && st.Peek() =='(')
{
st.Pop();//如果是右小括号且栈不为空,判断栈顶元素是否为左小括号
}
else
{
return false;//是,栈顶左括号出栈;否,右括号无法配对,返回false
}
}
else if(s[i] == '}')
{
if(st.Count != 0 && st.Peek() =='{')
{
st.Pop();
}
else
{
return false;
}
}
else if(s[i] == ']')
{
if(st.Count != 0 && st.Peek() =='[')
{
st.Pop();
}
else
{
return false;
}
}
}
return st.Count == 0;//如果栈为空,括号全部配对完,返回true
}
}
合并两个有序链表
public class Solution {
public ListNode MergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
public class Solution {
public IList<string> output = new List<string>();
public void SetGenerate(string str,int left,int right){
//若left和right 都为 0 则输出字符串
if(left == 0 && right == 0){
output.Add(str);
}
if(left > 0){
string leftstr = str + "(";
SetGenerate(leftstr,left-1,right+1);
}
if(right > 0){
string rightstr = str + ")";
SetGenerate(rightstr,left,right-1);
}
}
public IList<string> GenerateParenthesis(int n) {
SetGenerate("",n,0);//left需要 n 个,right则看字符串中的"("而定
return output;
}
}
合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode MergeKLists(ListNode[] lists) {
if(lists == null || lists.Length == 0) {
return null;
}
return MergeKLists(lists, 0, lists.Length-1);
}
private ListNode MergeKLists(ListNode[] lists, int left, int right) {
if(left == right) {
return lists[left];
}
int mid = left + (right-left)/2;
ListNode l1 = MergeKLists(lists, left, mid);
ListNode l2 = MergeKLists(lists, mid+1, right);
return MergeTwoLists(l1, l2);
}
private ListNode MergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) {
return l2;
}
if(l2 == null) {
return l1;
}
ListNode dummy = new ListNode();
ListNode p1 = l1, p2 = l2, cur = dummy;
while(p1 != null && p2 != null) {
if(p1.val < p2.val) {
cur.next = p1;
cur = cur.next;
p1 = p1.next;
} else {
cur.next = p2;
cur = cur.next;
p2 = p2.next;
}
}
cur.next = p1 == null ? p2 : p1;
return dummy.next;
}
}
删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
public class Solution {
public int RemoveDuplicates(int[] nums) {
if(nums.Length == 0){return 0;}
int slow = 0, fast = 1;
while(fast < nums.Length){
if(nums[fast] != nums[slow]){
slow = slow + 1;
nums[slow] = nums[fast];
}
fast = fast + 1;
}
return slow + 1;
}
}
两数相除
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
public class Solution {
public int Divide(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == int.MinValue) {
if (divisor == 1) {
return int.MinValue;
}
if (divisor == -1) {
return int.MaxValue;
}
}
// 考虑除数为最小值的情况
if (divisor == int.MinValue) {
return dividend == int.MinValue ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
bool rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
int left = 1, right = int.MaxValue, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
bool check = quickAdd(divisor, mid, dividend);
if (check) {
ans = mid;
// 注意溢出
if (mid == int.MaxValue) {
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
// 快速乘
public bool quickAdd(int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z != 0) {
if ((z & 1) != 0) {
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
}
}
搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
public class Solution {
public int Search(int[] nums, int target) {
int i;
for(i=0;i<nums.Length-1;i++)
if(nums[i]>nums[i+1])
break;
if(nums[0]>target)
for(int j=i+1;j<nums.Length;j++)
{
if(nums[j]==target)
return(j);
}
else
{
for(int j=0;j<i+1;j++){
if(nums[j]==target)
return(j);
}
}
return(-1);
}
}
在排序数组中查找元素的第一个和最后一个位置
public class Solution {
public int[] SearchRange(int[] nums, int target) {
int[] res = new int[] { -1,-1};
int left = 0;
int right = nums.Length;
while (left<=right)
{
int mid = left + (right - left) / 2;
if (mid>nums.Length-1)
{
return res;
}
if (nums[mid]==target)
{
for (int i = mid; i < nums.Length; i++)
{
if (nums[i]==target)
{
res[1] = i;
}
}
for (int i = mid; i >= 0; i--)
{
if (nums[i] == target)
{
res[0] = i;
}
}
return res;
}
else if (nums[mid]<target)
{
left = mid + 1;
}
else if (nums[mid]>target)
{
right = mid - 1;
}
}
return res;
}
}
缺失的第一个正数
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
//c++代码
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for (int& num: nums) {
if (num <= 0) {
num = n + 1;
}
}
for (int i = 0; i < n; ++i) {
int num = abs(nums[i]);
if (num <= n) {
nums[num - 1] = -abs(nums[num - 1]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
};
接雨水(注重思路)
public class Solution {
public int Trap(int[] height) {
// 存柱子高度index
Stack<int> st = new Stack<int>();
int sum = 0;
for(int i = 0; i < height.Length; i++){
while(st.Count != 0 && height[i] > height[st.Peek()]){
int mid = st.Pop();
// 左边没柱子了,直接跳出
if(st.Count == 0) break;
// 比如 0 1 2 3 对应 1 0 0 1, 1 和 1 之间有 0 0 两个数,则间隔为 3 - 0 - 1 = 2
int distance = i - st.Peek() - 1;
// 最小高度
int minHeight = Math.Min(height[st.Peek()], height[i]) - height[mid];
sum += distance * minHeight;
}
st.Push(i);
}
return sum;
}
}
最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
public class Solution {
public int MaxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
foreach (int x in nums) {
pre = Math.Max(pre + x, x);
maxAns = Math.Max(maxAns, pre);
}
return maxAns;
}
}
跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int rightmost = 0;
for (int i = 0; i < n; ++i) {
if (i <= rightmost) {
rightmost = max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
}
return false;
}
};
爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
public class Solution {
public int ClimbStairs(int n) {
// 动态规划
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
}
二叉树的中序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
public IList<int> InorderTraversal(TreeNode root) {
List<int> list = new List<int>();
Stack<TreeNode> stack = new Stack<TreeNode>();
while(root!=null || stack.Count != 0)
{
while(root!=null)
{
stack.Push(root);
root = root.left;
}
root = stack.Pop();
list.Add(root.val);
root = root.right;
}
return list;
}
}
验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
public bool IsValidBST(TreeNode root) {
return check(root,null,null);
}
public bool check(TreeNode root,int? min,int? max){
if(null == root) return true;
if((null != min) && (root.val <= min)) return false;
if((null != max) && (root.val >= max)) return false;
return check(root.left,min,root.val) && check(root.right,root.val,max);
}
}