原题链接:力扣热题-HOT100
题解的顺序和题目的顺序一致,那么接下来就开始刷题之旅吧,1-8题见LeetCode-hot100题解—Day1
注:需要补充的是,如果对于每题的思路不是很理解,可以点击链接查看视频讲解,是我在B站发现的一个宝藏UP主,视频讲解很清晰(UP主用的是C++),可以结合视频参考本文的java代码。
9.回文数
思路:
一种思路是将x
转换为字符串,然后采用双指针分别指向首尾来判断首尾元素是否相同来解决,进阶的解法是将x
反转后与原来的x
比较是否相同来解决,反转的思路和第7题相同。
时间复杂度:
时间复杂度为O(n)
代码实现:
class Solution {
public boolean isPalindrome(int x) {
//负数不是回文数
if(x<0) return false;
int num = x;
int cur = 0;
//反转x
while(x!=0){
if(cur > (Integer.MAX_VALUE - x % 10) / 10) return false;
cur = cur * 10 + x % 10;
x /= 10;
}
return num == cur;
}
}
10.正则表达式匹配
思路:
本题采用动态规划来解决,创建一个二维数组来记录匹配的结果,然后遍历两个字符串,这里p[j]
有三种情况,分别为普通字符、'.'
和'*'
,前两种情况很好确定状态方程,第三种情况的关键在于需要匹配多少个字符,具体的思路参考正则表达式匹配-详细思路。
时间复杂度:
时间复杂度为O(mn)
。
代码实现:
class Solution {
public boolean isMatch(String s, String p) {
int n= s.length();
int m= p.length();
//在s和p前面加空格,便于初始状态的初始化
s = ' ' + s;
p = ' ' + p;
boolean[][] f = new boolean[n+1][m+1];
f[0][0] = true;
//将字符串转换为字符数组
char[] cs = s.toCharArray();
char[] cp = p.toCharArray();
for(int i=0;i<=n;i++){
for(int j=1;j<=m;j++){
// 如果下一个字符是 '*',则代表当前字符不能被单独使用,跳过
if (j + 1 <= m && cp[j + 1] == '*') continue;
if (i - 1 >= 0 && cp[j] != '*') {
// 对应了 cp[j] 为普通字符和 '.' 的两种情况
f[i][j] = f[i - 1][j - 1] && (cs[i] == cp[j] || cp[j] == '.');
}
if (cp[j] == '*') {
// 对应了 p[j] 为 '*' 的情况
f[i][j]=(j>=2 && f[i][j-2]) || (i>=1 && j>=2 && f[i-1][j-2] && (cs[i] == cp[j-1] || cp[j-1] == '.')) || (i>=2 && j>=2 && f[i-2][j-2] && ((cs[i-1]==cp[j-1] && cs[i]==cp[j-1]) || cp[j-1]=='.'));
}
}
}
return f[n][m];
}
}
注:有一些测试用例没有通过,目前还没找到解决办法,如果有小伙伴有好的方法,欢迎留言讨论。后续如果我解决了的话会来更新的!
11.盛最多水的容器
思路:
本题采用双指针来求解,由于盛水的体积为宽✖高,因此可以定义首尾两个指针,枚举出不同高度的体积并取最大值,在移动指针的过程中,应该移动高度较小的指针,因为盛水的高度是由短板来决的,如图,不同的颜色表示每次移动指针后得到的体积。视频讲解点击视频讲解-盛最多水的容器。
时间复杂度:
时间复杂度为O(n)
,空间复杂度O(1)
。
代码实现:
class Solution {
public int maxArea(int[] height) {
int ans = 0;
int l = 0;
int r =height.length - 1;
while(l < r){
ans = Math.max(ans,Math.min(height[l],height[r]) * (r-l));
if(height[l] < height[r]) l++;
else r--;
}
return ans;
}
}
12.整数转罗马数字
思路:
读完题目可以理解罗马数字是怎么组成的,由于本题给出的范围是3999,比较小,所以一个最简单的方法是将需要用到的千位,百位,十位,个位数全部存到对应的数组中,然后通过分解给出的num来逐个匹配和拼接。如果不是很理解可以点击视频详解-整数转罗马数字。
时间复杂度:
这段代码的时间复杂度为O(1)
,因为无论输入的num
大小如何,都只进行了一次计算,没有随输入规模增加而增加的循环或递归。
代码实现:
class Solution {
public String intToRoman(int num) {
String[] thousands = new String[]{"","M","MM","MMM"};
String[] hundreds = new String[]{"","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"};
String[] tens = new String[]{"","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"};
String[] ones = new String[]{"","I","II","III","IV","V","VI","VII","VIII","IX"};
return thousands[num / 1000] + hundreds[num % 1000 /100] + tens[num % 100 / 10] + ones[num % 10];
}
}
13.罗马数字转整数
思路:
本题和12题正好相反,根据12题我们已经知道了罗马数字组成的规律了,我们只需要遍历给出的罗马数字,累加罗马数字对应的整数即可,注意需要加上判断,因为罗马数字中有一些特殊的数字,当当前的字符代表的整数小于后面字符对应的整数时,结果需要减去当前字符对应的整数(如,4是IV
,是5-1
),当当前字符对应的整数大于后一个字符对应的整数后,结果加上当前字符对应的整数,下图给出一个直观的例子。详细的视频讲解点击视频讲解-罗马数字转整数
时间复杂度:
时间复杂度是O(n)
,其中n是字符串s
的长度。
代码实现:
class Solution {
public int romanToInt(String s) {
Map<Character,Integer> value =new HashMap<>();
value.put('I',1);
value.put('V',5);
value.put('X',10);
value.put('L',50);
value.put('C',100);
value.put('D',500);
value.put('M',1000);
int ans = 0;
char[] cs = s.toCharArray();
for(int i=0;i<s.length();i++){
if(i+1 < s.length() && value.get(cs[i]) < value.get(cs[i+1])){
ans -= value.get(cs[i]);
}else{
ans += value.get(cs[i]);
}
}
return ans;
}
}
14.最长公共前缀
思路:
本题采用暴力解法,以第一个字符串为基础,与数组中其他字符串比较,依次取出第一个字符串的每个字符,与后面的字符串的字符比较,当遇到某个字符串的长度溢出或者当两个字符不匹配时,截取第一个字符串,最后返回改字符串数组中的第一个字符串即为所求,详细的视频讲解点击视频讲解-最长公共前缀
时间复杂度:
这段代码的时间复杂度为O(n*m)
,其中n
是字符串数组的长度,m
是数组中最短字符串的长度。在最坏的情况下,需要比较所有字符串的每个字符,所以时间复杂度为O(n*m)
。
代码实现:
class Solution {
public String longestCommonPrefix(String[] strs) {
for(int i = 0;i < strs[0].length(); i++){
char c = strs[0].charAt(i);
for(int j = 1;j < strs.length;j++){
if(i == strs[j].length() || strs[j].charAt(i) != c){
return strs[0].substring(0,i);
}
}
}
return strs[0];
}
}
15.三数之和
思路:
本题采用枚举+双指针的做法,外循环对nums
的元素依次遍历,在遍历时寻找可以和外循环元素相加为0的元素对,内层循环采用首尾双指针进行遍历,但是这样做的前提是需要首先对数组进行排序,最后需要注意由于不能输出重复的结果,所以每次遍历时都要跳过相同元素。详细的讲解参考视频讲解-三数之和。
时间复杂度:
这段代码的时间复杂度为O(n^2)
,其中n
为数组nums
的长度。主要的时间复杂度来源于两层循环,外层循环遍历数组nums
,内层循环使用双指针法遍历数组中的剩余元素。在最坏情况下,内层循环的时间复杂度为O(n)
,所以总的时间复杂度为O(n^2)
。
代码实现:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
//对数组进行排序,方便采用首尾指针进行遍历
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
//跳过重复元素
if(i > 0 && nums[i] == nums[i-1]) continue;
int l = i + 1;
int r = nums.length - 1;
int target = 0 - nums[i];
while(l < r){
if(nums[l] + nums[r] == target){
ans.add(Arrays.asList(nums[i],nums[l],nums[r]));
//跳过重复元素
while(l < r && nums[l] == nums[ l + 1]) l++;
while(l < r && nums[r] == nums[r - 1]) r--;
l++;
r--;
}else if(nums[l] + nums[r] < target){
l++;
}else{
r--;
}
}
}
return ans;
}
}
知识拓展:Java
中动态数组的使用(ArrayList
和List
)
区别:
1.List
是ArrayList
的泛型等效类,List
是一个接口,而ArrayList
是一个类,它实现了List
接口,所以List
不能被构造,List list=new List()
这种写法是错误的,而ArrayList
就可以被构造。List list = new ArrayList();
这句创建了一个ArrayList
的对象后把向上转型成了List
。此时它是一个List
对象了,有些ArrayList
有但是List
没有的属性和方法,它就不能再用了。而ArrayList list=new ArrayList();
创建一对象则保留了ArrayList
的所有属性。
2.List
相比ArrayList
来说更加安全,因为ArrayList
加入的数据为object
类型,需要装箱和拆箱操作。List
声明时就决定了类型,所以是类型安全的,省掉了装箱与拆箱的过程,并且效率更高。
用法:
1.定义
ArrayList
:
ArrayList list1 = new ArrayList();
List
:
List<List<Integer>> list2 = new ArrayList<>();
2.添加元素
ArrayList
:
(1)添加单个元素
list1.Add(1);
(2)添加多个元素
list1.addAll(Arrays.asList(a,b,c));
List
:
(1)添加单个元素
list2.Add(1);
(2)添加多个元素
list2.add(Arrays.asList(a,b,c));
3.返回
需要注意的是ArrayList
直接返回结果会报错,如果添加了多个元素后返回list
,则需要使用List
定义(eg.15题中如果定义时使用ArrayList
会报错)。具体的原因我还没搞清楚,如果有小伙伴了解的欢迎留言交流,后续如果了解了这块内容会来更新哒~
16.最接近的三数之和
思路:
本题的思路和15题类似,只是将判断条件改为比较三个元素之和与目标元素的差值的最小值,在求解时,我们还是先要将数组进行排序。详细的视频讲解参考视频讲解-最接近的三数之和。
时间复杂度:
时间复杂度为O(n^2)
,其中n为数组nums
的长度。
代码中使用了三重循环,外层循环遍历数组元素,内层循环使用双指针l
和r
进行夹逼求解。在最坏情况下,内层循环的执行次数为O(n)
,因此总体的时间复杂度为O(n^2)
。
排序数组的时间复杂度为O(nlogn)
,对数组进行排序只需要执行一次,不会影响总体的时间复杂度。
代码实现:
class Solution {
public int threeSumClosest(int[] nums, int target) {
int ans = nums[0] + nums[1] +nums[2];
Arrays.sort(nums);
for(int i = 0;i < nums.length;i++){
int l = i + 1;
int r = nums.length - 1;
while(l < r){
int sum = nums[i] + nums[l] + nums[r];
if(Math.abs(sum - target) < Math.abs(ans - target)) ans = sum;
if(sum < target){
l++;
}else{
r--;
}
}
}
return ans;
}
}
待续…