目录
本文从 Leetcode 中精选大概 200 左右的题目,去除了某些繁杂但是没有多少算法思想的题目,同时保留了面试中经常被问到的经典题目,题中代码均为java语言
题目集合目录地址:github地址
算法思想
双指针
633. 平方数之和——力扣
题目以及示例:
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
示例 3:
输入:c = 4
输出:true
示例 4:
输入:c = 2
输出:true
示例 5:
输入:c = 1
输出:true
提示:
0 <= c <= 231 - 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sum-of-square-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码:
class Solution {
public boolean judgeSquareSum(int c) {
if(c<0) return false;
//if(c==1||c==0) return true;
int i=0,j=(int)Math.sqrt(c);
while(i<=j){
int sum=i*i+j*j;
if(sum==c)
return true;
else if(sum > c)
j--;
else
i++;
}
return false;
}
}
坑:
本次记录主要是记录边界值方面的坑以及我第一次做没有注意到的地方,主要考虑以下几点:
在题目中:
(1)非负整数c可能为0,
(2)整数a、b可能为负数及0
(3)整数a、b可能相等
不注意可能因此漏掉0^2+0^2=0的情况。
在代码中:
(1)c<0这个条件在测试用例中并没有,但是最好判断一下
(2)注意跳出循环判断条件i<=j
(3)注意i=0开始
(4)0可以被开方
345. 反转字符串中的元音字母——力扣
难度简单162收藏分享切换为英文接收动态反馈
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入:"hello"
输出:"holle"
示例 2:
输入:"leetcode"
输出:"leotcede"
提示:
- 元音字母不包含字母 "y" 。
代码:
class Solution {
public String reverseVowels(String s) {
if(s==null)return null;
int i=0,j=s.length()-1;
char[] s2=s.toCharArray();
while(i<j){
while(i<j){
if(s2[i]=='a'||s2[i]=='e'||s2[i]=='i'||s2[i]=='o'||s2[i]=='u'||
s2[i]=='A'||s2[i]=='E'||s2[i]=='I'||s2[i]=='O'||s2[i]=='U')
break;
else
i++;
}
while(i<j){
if(s2[j]=='a'||s2[j]=='e'||s2[j]=='i'||s2[j]=='o'||s2[j]=='u'||
s2[j]=='A'||s2[j]=='E'||s2[j]=='I'||s2[j]=='O'||s2[j]=='U')
break;
else
j--;
}
char temp=s2[i];
s2[i]=s2[j];
s2[j]=temp;
i++;j--;
}
return String.valueOf(s2);
}
}
坑:
1、String字符串在java中是常量不能改变,将String字符串转字符数组便于字符串的修改操作,需要用到两个String字符串与字符数组相互转换的函数:
String字符串转字符数组:s.toCharArray();
字符数组转String字符串:String.valueOf(s2);
2、注意题目说的是元音字母,没有说大小写,也就是说大小写字母都需要判断!!!
3、最后自己忘记了一个细节,字符完成对调之后还需要将i与j的值相应改变,否则程序超时
680. 验证回文字符串 ——力扣
难度简单365收藏分享切换为英文接收动态反馈
给定一个非空字符串 s
,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:
- 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
代码:
class Solution {
public boolean validPalindrome(String s) {
if(s==null) return false;
int low=0,high=s.length()-1;
while(low<high){
if(s.charAt(low)!=s.charAt(high)){
return validPalindrome(s,low+1,high)||validPalindrome(s,low,high-1);
}
else{
low++;high--;
}
}
return true;
}
public boolean validPalindrome(String s,int low,int high){
while(low<high){
if(s.charAt(low)==s.charAt(high)){
low++;high--;
}
else
return false;
}
return true;
}
}
坑:
1、本来想靠直接判断(i+1)、(j-1)个字符串哪个与另外一会边相等,然后继续后面的判断,直到再次到不相同字符的时候返回false,但是第一次遇到不同字符时会难以确定如何取舍。
2、官方的解题技巧需要学习!
3、在允许最多删除一个字符的情况下,同样可以使用双指针,通过贪心算法实现。初始化两个指针 low 和 high 分别指向字符串的第一个字符和最后一个字符。每次判断两个指针指向的字符是否相同,如果相同,则更新指针,令 low = low + 1 和 high = high - 1,然后判断更新后的指针范围内的子串是否是回文字符串。如果两个指针指向的字符不同,则两个字符中必须有一个被删除,此时我们就分成两种情况:即删除左指针对应的字符,留下子串 s[low + 1], s[low + 1], ..., s[high],或者删除右指针对应的字符,留下子串 s[low], s[low + 1], ..., s[high - 1]。当这两个子串中至少有一个是回文串时,就说明原始字符串删除一个字符之后就以成为回文串。
88. 合并两个有序数组
难度简单998收藏分享切换为英文接收动态反馈
给你两个有序整数数组 nums1
和 nums2
,请你将 nums2
合并到 nums1
中,使 nums1
成为一个有序数组。
初始化 nums1
和 nums2
的元素数量分别为 m
和 n
。你可以假设 nums1
的空间大小等于 m + n
,这样它就有足够的空间保存来自 nums2
的元素。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[i] <= 109
自己的代码:
思路:挨个比较nums1数组与nums2数组对应值,当出现nums1数组某值大于nums2数组,则将nums1数组从该值开始后移,空出该值的位置,赋值为nums2数组的值,最后如果nums2数组还有剩余则全部放到nums1数组末尾。
package leetcode;
import java.util.Arrays;
public class Solution {
static String s="hello";
public static void main(String[] args) {
int[] nums1={1,2,3,0,0,0};
int[] nums2={2,5,6};
int m=3,n=3;
merge(nums1,m,nums2,n);
System.out.println(Arrays.toString(nums1));
//System.out.println(reverseVowels(s));
}
//合并两个数组
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int i=0,j=0;
while(i<m&&j<n){
if(nums1[i]<=nums2[j])
i++;
else{
move(nums1,i,m);
nums1[i]=nums2[j];
i++;j++;m++;
}
}
while(j<n){
nums1[i++]=nums2[j++];
}
}
//后移操作
public static void move(int[] num1,int index,int m){
for(int i=m;i>index;i--)//从位置m处开始后移,直到空白位置后一个结束
{
num1[i]=num1[i-1];
}
}
//最多删去一个字符,可以成为回文吗
public static String reverseVowels(String s) {
if(s==null)return null;
int i=0,j=s.length()-1;
char[] s2=s.toCharArray();
while(i<j){
while(i<j){
if(s2[i]=='a'||s2[i]=='e'||s2[i]=='i'||s2[i]=='o'||s2[i]=='u')
break;
else
i++;
}
while(i<j){
if(s2[j]=='a'||s2[j]=='e'||s2[j]=='i'||s2[j]=='o'||s2[j]=='u')
break;
else
j--;
}
char temp=s2[i];
s2[i]=s2[j];
s2[j]=temp;
i++;j--;
}
return String.valueOf(s2);
}
}
官方代码:
我们可以使用双指针方法。这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = 0, p2 = 0;
int[] sorted = new int[m + n];
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
}
坑:
1、在自己的思路中,当nums1数组存在后移操作时,不能忘记将表示nums1长度的m值+1
2、我自己的代码与官方的代码执行时间一致,但是由于每次后移操作比较费时,推测在测试用例中没有很长的数组,否则先将排序结果存放于新数组,排序结束再将新数组引用赋给nums,执行时间会明显大于我的思路。
141. 环形链表
难度简单1125收藏分享切换为英文接收动态反馈
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true
。 否则,返回 false
。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
示例 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
或者链表中的一个 有效索引 。
代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null) return false;
ListNode p1=head,p2=head.next;
while(p2!=null&&p1!=null&&p2.next!=null){
if(p1==p2)
return true;
else{
p1=p1.next;
p2=p2.next;
if(p2.next!=null)
p2=p2.next;
}
}
return false;
}
}
坑:
这道题思路到没有坑到我,只是题目乍一看有一点当我不解,因此记录一下
题目的意思和给出的预留代码都显示的是对链表操作,但是给的示例输入时一个数组+一个数字,只看示例的的话会认为只需要判断数字的大小就行了,但是仔细一想会知道输入给的数组是建立链表的数据,数字是链表尾部指向的节点位置。
这样的输入和实际处理的东西有这样的差别还是第一次见到,也是我做过的题目不够多,希望多见一些,真到用起来的时候希望能够得心应手。
524. 通过删除字母匹配到字典里最长单词
难度中等149收藏分享切换为英文接收动态反馈
给你一个字符串 s
和一个字符串数组 dictionary
作为字典,找出并返回字典中最长的字符串,该字符串可以通过删除 s
中的某些字符得到。
如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
示例 2:
输入:s = "abpcplea", dictionary = ["a","b","c"]
输出:"a"
提示:
1 <= s.length <= 1000
1 <= dictionary.length <= 1000
1 <= dictionary[i].length <= 1000
s
和dictionary[i]
仅由小写英文字母组成
通过次数35,522提交次数75,184
自己的代码:
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
String temp="";
for(int i=0;i<dictionary.size();i++){
int j=0,k=0;
String s2=dictionary.get(i);
while(j<s.length()&&k<s2.length()){
if(s2.charAt(k)!=s.charAt(j))
j++;
else{
k++;j++;
}
}
if(k==s2.length()){
if(temp==null)
temp=s2;
else if(temp.length()<s2.length()||temp.length()==s2.length()&&temp.compareTo(s2)>0)
temp=s2;
}
}
return temp;
}
// 与compareTo效果差不多
// public boolean compare(String s1,String s2){
// int i=0;
// while(i<s1.length()&&i<s2.length()){
// if(Integer.valueOf(s1.charAt(i))<Integer.valueOf(s2.charAt(i)))
// return true;
// else
// return false;
// }
// return false;
// }
}
坑:
1、题目中:
题目倒是不难理解,但是“字典序”这个词很久没见到了,第一次提交的时候没注意它导致失败。
(1)字典序:将多个字符串的同一位置的字符按照26个字母的顺序进行比对。a最小,z最大。
a < b;
aa < ab; 因为第二位置上,前面字符串是a,后面字符串是b,所以是小于关系,以此类推。
(2)题目中说的空字符串不能是null,而必须是""
2、代码中:
代码中失误主要还是因为自己的粗心大意
(1)java中与C/c++很大的不同是没有直接“对象.属性”的,必须通过方法获得属性值,因此length()要注意
(2)字符串常用操作:
int compareTo(String anotherString)
- 如果参数字符串等于此字符串,则返回值 0;
- 如果此字符串小于字符串参数,则返回一个小于 0 的值;
- 如果此字符串大于字符串参数,则返回一个大于 0 的值。
(3)字符、字符串及数字之间的转换
1)string 和int之间的转换
1.string转换成int:Integer.valueOf("12")
2.int转换成string:String.valueOf(12)
2)char和int之间的转换
1.首先将char转换成string
String str=String.valueOf('2')
2.转换
Integer.valueof(str) 或者Integer.PaseInt(str)
Integer.valueof返回的是Integer对象,Integer.paseInt返回的是int
215. 数组中的第K个最大元素
难度中等1168收藏分享切换为英文接收动态反馈
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和
k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和
k = 4
输出: 4
提示:
1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104
通过次数370,913提交次数573,600
1、java内置排序,时间复杂度 O(NlogN),空间复杂度 O(1)
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
2、堆排序代码:时间复杂度 O(NlogK),空间复杂度 O(K)。
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
for (int val : nums) {
pq.add(val);
if (pq.size() > k) // 维护堆的大小为 K
pq.poll();
}
return pq.peek();
}
3、代码:时间复杂度 O(N),空间复杂度 O(1)
class Solution {
public int findKthLargest(int[] nums, int k) {
k=nums.length-k;
int low=0,high=nums.length-1;
while(low<high){
int j=Quik(nums,low,high);
if(k==j)
break;
else if(k<j)
high=j-1;
else
low=j+1;
}
return nums[k];
}
public int Quik(int[] nums,int low,int high){
int temp=nums[low];
while(low<high){
while(low<high&&nums[high]>=temp)high--;
nums[low]=nums[high];
while(low<high&&nums[low]<=temp)low++;
nums[high]=nums[low];
}
nums[high]=temp;
return high;
}
}
坑:
1、本题若使用java内置方法做,则并没有什么难度,重点就是理解并实现排序过程。
2、堆排序也是借助java的PriorityQueue类,PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。
3、重点放在第三个代码,实现快速排序,也是面试最容易问到的。本代码相比leetcode官方代码更好理解,快排思路不变,在边界和值交换的处理上更简洁。
1)边界,Quik中的外循环跳出后low是中等于high,因此不必考虑基准值最后的下标用哪个。
2)值交换,此处使用一个临时变量保存基准值,最后将其赋给low或high的位置,其他细节看代码。
4、可以考虑实现堆排序,涉及要实现的函数有:add()、siftUp()、peek()、poll()、remove(Object o),参考:PriorityQueue的用法和底层实现原理
347. 前 K 个高频元素
难度中等800收藏分享切换为英文接收动态反馈
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105
k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
通过次数176,310提交次数283,525
代码:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
for(int num:nums)
map.put(num,map.getOrDefault(num,0)+1);
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);//重写compare,使之通过比较map中两个输入人key对应的value来确定大小
}
});
//PriorityQueue<Integer> pq = new PriorityQueue<>();
for (Integer key : map.keySet()) {
if (pq.size() < k) {//遍历map将数组中的值存入优先队列,并维护堆大小为k,当堆大小大于k时,将堆顶端的数最小值去掉
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
int[] res =new int[k];
int i=0;
while (!pq.isEmpty()) {
res[i++]=pq.remove();
}
return res;
}
}
坑:
本题思路与上一题很像,用Map字典将数组值及其频率统计之后,借助java的优先队列使用堆排序即可较为轻松的解决。其中堆排序的时候要注意两个点:
1、与上一题一样,维护小根堆大小为k,超过k时则将堆顶的低频数值出堆
2、重写优先队列的比较方法compare,使之比较的是输入key对应的value(频率),这样才能达到依据频率高低排序。
3、map.getOrDefault(a,b),返回key=a的value,否则返回b,正好用于统计操作
贪心算法
452. 用最少数量的箭引爆气球
难度中等432收藏分享切换为英文接收动态反馈
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x
start
,x
end
, 且满足 xstart ≤ x ≤ x
end
,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points
,其中 points [i] = [xstart,xend]
,返回引爆所有气球所必须射出的最小弓箭数。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2 解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]] 输出:4
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]] 输出:2
示例 4:
输入:points = [[1,2]] 输出:1
示例 5:
输入:points = [[2,3],[2,3]] 输出:1
提示:
1 <= points.length <= 104
points[i].length == 2
-231 <= xstart < xend <= 231 - 1
代码:
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length==0) return 0;
// Arrays.sort(points,new Comparator<int[]>() {
// @Override
// public int compare(int[] a,int[] b){
// //按区间左值升序排序
// //实现 compare() 函数时避免使用 return o1[1] - o2[1]; 这种减法操作,防止溢出。
// return (a[1]>b[1]?1:(a[1]==b[1]?0:-1));
// }
// });
//按照右区间值升序排序,若按照左区间值升序排序则需要多遍历一次数组
//重写comparingInt比较函数,传入比较的对象为每个数组第二个值。按照结尾排序。
Arrays.sort(points,Comparator.comparingInt(o->o[1]));
int sum=1;
int end=points[0][1];
for(int i=1;i<points.length;i++)
{
if(end>=points[i][0])
continue;
sum++;
end=points[i][1];
}
return sum;
}
}
坑:
本题思路上来看,先将区间按照右值排序,然后每次从区间右值起始寻求能够刺破的气球数量。