数据结构与算法
LeetCode必刷
1. 两数之和(leetcode-1)
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] indexs=new int[2]; //定义返回数组
Map map=new HashMap<>(); //定义map用于存储值数组里的值与它的index
for (int i = 0; i < nums.length; i++) { //对数组进行遍历
int needNum=target-nums[i]; //需要的值:比如目标值为9,index=0的值为2,needNum则为7
if (map.containsKey(needNum)){ //判断需要的值是否在map中
indexs[0]=map.get(needNum);//从map中取出needNum的下标
indexs[1]=i; //存储此时的index
return indexs;
}
map.put(nums[i],i); //把数组中值进行存储到map中
}
return indexs;
}
}
2. 回文数(leetcode-9)
给你一个整数 x
,如果 x
是一个回文整数,返回 true
;否则,返回 false
。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,
121
是回文,而123
不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
class Solution {
public boolean isPalindrome(int x) {
String str = String.valueOf(x);
String reverseStr = new StringBuilder(str).reverse().toString();
if (str.equals(reverseStr)){
return true;
}else return false;
}
}
3. 整数反转(leetcode-7)
给你一个 32 位的有符号整数 x
,返回将 x
中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1]
,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
输入:x = 123
输出:321
示例 2:
输入:x = -123
输出:-321
class Solution {
public int reverse(int x) {
int result=0;
while (x!=0){
//最小位的数
int lowBit=x%10;
//去掉最低位后的数 例:1234->123
x=x/10;
// int范围: -2147483648——2147483647
/**
* 反转时可能会超过int最大值,发生溢出
* result*10+lowBit>Integer.MAX
* result>=Integer.MAX/10 可能溢出
* (1) result>MAX 一定溢出
* (2) result=MAX && lowBit>7 一定溢出
*
*负数同理
*/
if (result > Integer.MAX_VALUE/10 || (result == Integer.MAX_VALUE/10 && lowBit > 7))
return 0;
if (result < Integer.MIN_VALUE/10 || (result == Integer.MIN_VALUE/10 && lowBit < -8))
return 0;
result=result*10+lowBit; //例子:123-->3-->3*10+2-->(3*10+2)*10+1=321
}
return result;
}
}
4. 二进制求和
给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1
和 0
。
示例 1:
输入: a = "11", b = "1"
输出: "100"
示例 2:
输入: a = "1010", b = "1011"
输出: "10101"
class Solution {
public String addBinary(String a, String b) {
/**
* jinwei:0 0 0 1
* a:0 0 1 1
* b:0 1 1 1
* 0 1 2 3
*/
int alen = a.length();
int blen = b.length();
int jinwei = 0;
String result = "";
while (alen > 0 || blen > 0) {
int sum = 0;
sum += jinwei;
if (alen > 0) {
String aLastStr = a.substring(alen - 1, alen);
int aLastInt = Integer.parseInt(aLastStr);
sum += aLastInt;
alen--;
}
if (blen > 0) {
String bLastStr = b.substring(blen - 1, blen);
int bLastInt = Integer.parseInt(bLastStr);
sum += bLastInt;
blen--;
}
switch (sum) {
case 2:
jinwei = 1;
result = "0" + result;
break;
case 3:
jinwei = 1;
result = "1" + result;
break;
default:
jinwei = 0;
result = sum + "" + result;
}
}
if (jinwei == 1) {
result = "1" + result;
}
return result;
}
}
5. 罗马数字转整数
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2
写做 II
,即为两个并列的 1 。12
写做 XII
,即为 X
+ II
。 27
写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
-
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。 -
X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。 -
C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
class Solution {
public int romanToInt(String s) {
int sum = 0;
int preValue = getValue(s.charAt(0));
for (int i = 1; i < s.length(); i++) {
int value = getValue(s.charAt(i));
if (preValue < value) {
sum -= preValue;
} else {
sum += preValue;
}
preValue = value;
}
sum += preValue;
return sum;
}
private int getValue(char ch) {
switch (ch) {
case 'I':
return 1;
case 'V':
return 5;
case 'X':
return 10;
case 'L':
return 50;
case 'C':
return 100;
case 'D':
return 500;
case 'M':
return 1000;
default:
return 0;
}
}
}
6. x的平方根(leetcode-69)
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
class Solution {
/**
* 二分查找
* 例子: x=10
* left:1 mid:(1+10+1)/2=6 right:10
* 36>10
* left:1 mid:(1+5+1)/2=3 right:5
* 9<=10
* left:3 mid:(3+5+1)/2=4 right:5
* 16>10
* left:3 mid:3 right:3 跳出循环,返回 3
*
*/
public int mySqrt(int x) {
int left = 1;
int right = x;
while (left < right) {
/*
注意(left+right)/2,(left+right+1)/2,
当left+right为偶数时,在int的取整下,+1实际上是对整数+1/2,对结果无影响,
而当left+right为奇数时,比如1+10=11 中间值为5.5,取整为5,则精度丢失0.5,+1为6.5,取整为6,就不丢失了
mid = left + (right - left) / 2 和 mid = (left + right) / 2,相比后者也存在溢出
*/
int mid = (left+right+1) / 2;
//mid*mid<=x
if (mid <= x / mid) {
left = mid;
} else {
right = mid - 1;
}
}
return right;
}
}
7. 赎金信(leetcode-383)
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] arr = new int[26];
int temp;
for (int i = 0; i < magazine.length(); i++) {
//ASCII码中a为97
temp = magazine.charAt(i) - 'a';
arr[temp]++;
}
for (int i = 0; i < ransomNote.length(); i++) {
temp = ransomNote.charAt(i) - 'a';
//对于金信中的每一个字符都在数组中查找
//找到相应位减一,否则找不到返回false
if (arr[temp] > 0) {
arr[temp]--;
} else {
return false;
}
}
return true;
}
}
8. 唯一摩尔斯密码词(leetcode-804)
为了方便,所有 26
个英文字母的摩尔斯密码表如下:
[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."]
给你一个字符串数组 words
,每个单词可以写成每个字母对应摩尔斯密码的组合。
- 例如,
"cab"
可以写成"-.-..--..."
,(即"-.-."
+".-"
+"-..."
字符串的结合)。我们将这样一个连接过程称作 单词翻译 。
对 words
中所有单词进行单词翻译,返回不同 单词翻译 的数量。
示例 1:
输入: words = ["gin", "zen", "gig", "msg"]
输出: 2
解释:
各单词翻译如下:
"gin" -> "--...-."
"zen" -> "--...-."
"gig" -> "--...--."
"msg" -> "--...--."
共有 2 种不同翻译, "--...-." 和 "--...--.".
示例 2:
输入:words = ["a"]
输出:1
class Solution {
public int uniqueMorseRepresentations(String[] words) {
String[] table = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
Set set = new HashSet<>();
for (String word : words) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < word.length(); i++) {
int index = (int) (word.charAt(i) - 'a');
stringBuilder.append(table[index]);
}
set.add(stringBuilder.toString());
}
System.out.println(set);
return set.size();
}
}
9. 丢失的数字(leetcode-268)
给定一个包含 [0, n]
中 n
个数的数组 nums
,找出 [0, n]
这个范围内没有出现在数组中的那个数。
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
class Solution {
public int maxProfit(int[] prices) {
int minPrice=Integer.MAX_VALUE;
int maxProfit=0;
for (int i = 0; i < prices.length; i++) {
if (prices[i]maxProfit){
maxProfit=profit;
}
}
}
return maxProfit;
}
}
10. 斐波那契数列(leetcode-509)
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
示例 1:
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
class Solution {
//方法一
public int fib(int n) {
if (n <= 1) {
return n;
}
int a = 0, b = 1, c = 0;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
//方法二
public int fib(int n) {
int[] fib = new int[n + 2];
fib[0] = 0; // 第 0 个斐波那契数
fib[1] = 1; // 第 1 个斐波那契
for(int i = 2; i <= n; i++) {
fib[i] = fib[i-1] + fib[i-2];
}
return fib[n];
}
}
11. 字符串的排列(leetcode-567) ⭐️
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
换句话说,s1
的排列之一是 s2
的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
示例 2:
输入:s1= "ab" s2 = "eidboaoo"
输出:false
//双指针,滑动窗口算法
class Solution {
public boolean checkInclusion(String s1, String s2) {
int len1 = s1.length(), len2 = s2.length();
//s1长度大于s2时,s2必定不会包含s1,直接返回false
if (len1 > len2) {
return false;
}
//创建字典
int[] arr1 = new int[26], arr2 = new int[26];
//以s1的长度为基准,对应字典加1
for (int i = 0; i < len1; i++) {
arr1[s1.charAt(i) - 'a']++;
arr2[s2.charAt(i) - 'a']++;
}
//滑动窗口,定义左右边界
int left = 0, right = len1 - 1;
//循环条件右边界小于s2长度
while (right < len2) {
//如果刚开始s2的前三个字母与s1相等直接返回true
if (Arrays.equals(arr1, arr2)) return true;
//右边界右移动
right++;
//移动后的右边界不能等于s2的长度,否则就数组越界了
if (right != len2) {
//右边界右移一位后当前字母存入字典
arr2[s2.charAt(right) - 'a']++;
}
//同时左边界当前字母要从字典中删除,删除后也同样右移,实现第一次滑动窗口,以此循环,直到两字典相同
arr2[s2.charAt(left) - 'a']--;
left++;
}
//循环结束,则返回false
return false;
}
}
12. 快乐数(leetcode-202)
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19
输出:true
/**
*两种情况:1.死循环 2.为1
* 把每次平方和存入set,有重复元素则返回false,说明进入死循环始终不会为1
*/
class Solution {
public boolean isHappy(int n) {
HashSet set = new HashSet<>();
while (n != 1) {
n = sum(n);
if (!set.add(n)) return false;
}
return true;
}
public int sum(int n) {
int sum = 0;
while (n != 0) {
sum += (n % 10) * (n % 10);
n = n / 10;
}
return sum;
}
}
14. 丑数(leetcode-263)
丑数 就是只包含质因数 2
、3
和 5
的正整数。
给你一个整数 n
,请你判断 n
是否为 丑数 。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:n = 6
输出:true
解释:6 = 2 × 3
//对 n 反复除以 2,3,5,直到 n 不再包含质因数 2,3,5。若剩下的数等于 1,则说明 n 不包含其他质因数,是丑数;否则,说明 n 包含其他质因数,不是丑数。
class Solution {
public boolean isUgly(int n) {
if (n < 0) {
return false;
}
if (n <= 0) {
return false;
}
int[] factors = {2, 3, 5};
for (int factor : factors) {
while (n % factor == 0) {
n /= factor;
}
}
return n == 1;
}
}
15. 快乐数(leetcode-507)
对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」。
给定一个 整数 n
, 如果是完美数,返回 true
;否则返回 false
。
示例 1:
输入:num = 28
输出:true
解释:28 = 1 + 2 + 4 + 7 + 14
1, 2, 4, 7, 和 14 是 28 的所有正因子。
class Solution {
public boolean checkPerfectNumber(int num) {
//1是任何数的因子,sum直接从1开始
int sum = 1;
//从2开始遍历,到num的开方结束,因为因数都是成对出现的
//比如36--> [2,18][3,12][4,9][6,6],2~6对应的因数可以通过num除以1~6获得,当然36并不是快乐数举例而已
for (int i = 2; i <= Math.sqrt(num); i++) {
if (num % i == 0) {
//加上因数本身
sum += i;
//加上与本身成对的因数
sum = sum + num / i;
//如果因数为开方时,要减去一个,不然算了两次,比如[6,6]
if (i == Math.sqrt(num)) {
sum -= i;
}
}
}
//如果num等于sum,并且不等于1时返回true
return sum == num && sum != 1;
}
}