盛夏未至,山河已秋
数组
一、删除有序数组的重复项
- 题目描述:给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。将最终结果插入 nums 的前 k 个位置后返回 k 。不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
概述:按照原来的顺序在数组中不重复的元素移动到数组的前面。
- 解题思路:
利用双指针,如果左右指针的数值相同,那么只需要移动右指针即可;如果两个指针的数值不同,那么就移动左指针,再将右指针的数值赋值给左指针,最后再移动右指针。
- 代码部分:
class Solution {
public int removeDuplicates(int[] nums) {
//数组为空,直接返回0
if(nums.length < 0) {
return 0;
}
//设置左右指针
int left = 0;
for(int right = 1; right < nums.length; right++){
//如果右指针的数与左指针不同,那么就移动左指针,并将右指针的值赋值给左指针
if(nums[left] != nums[right]){
left++;
nums[left] = nums[right];
}
}
return left + 1;
}
}
二、买卖股票的最佳时机 II
- 题目描述:给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多只能持有 一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。
- 解题思路:
贪心算法版:
假设相邻两天都会进行买卖,如果利润是正的就算到总利润里,否则就舍弃
class Solution {
public int maxProfit(int[] prices) {
int sum = 0;
for(int i = 0; i < prices.length - 1; i++){
sum += Math.max(0, prices[i + 1] - prices[i]);
}
return sum;
}
}
动态规划版:
两个变量分别记录当前手里有股票和没股票的最大利润,根据手里股票前一天和今天的持有情况不断更新最大利润,最后返回手里没有股票的最大利润
class Solution {
public int maxProfit(int[] prices) {
//当前手里有股票和当前手里没股票的最大利润
int hold = -prices[0];
int noHold = 0;
//利用递推公式不断更新最大利润
for(int i = 1; i < prices.length; i++){
hold = Math.max(hold, noHold - prices[i]);
noHold = Math.max(noHold, hold + prices[i]);
}
//返回最后一天手里没有股票的最大利润
return noHold;
}
}
三、旋转数组
- 问题描述:给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
- 解题思路:
设置一个翻转的方法,调用这个方法完成整体翻转和局部翻转即可
- 代码部分:
class Solution {
public void rotate(int[] nums, int k) {
//因为可能旋转几个来回
k %= nums.length;
//整体翻转,部分翻转
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int [] arr, int start, int end){
while(start < end){
//交换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
//更新索引
start++;
end--;
}
}
}
四、存在重复元素
- 问题描述:给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
- 解题思路:
(1)版本一:排序+遍历,过了但很慢 19ms
(2)版本二:遍历添加到集合中,通过数组大小和集合大小来判断 14ms
(3)版本三:在向集合中添加元素的时候就判断 4ms
版本一
class Solution {
public boolean containsDuplicate(int[] nums) {
//版本一:排序+遍历,过了但很慢 19ms
Arrays.sort(nums);
for(int i = 0; i < nums.length - 1; i++){
if(nums[i + 1] == nums[i]){
return true;
}
}
return false;
}
}
版本二
class Solution {
public boolean containsDuplicate(int[] nums) {
//版本二:遍历添加到集合中,通过数组大小和集合大小来判断
Set<Integer> set = new HashSet<Integer>();
for(int i = 0; i < nums.length; i++){
set.add(nums[i]);
}
//说明有重复的元素
if(set.size() < nums.length){
return true;
}
return false;
}
}
版本三
class Solution {
public boolean containsDuplicate(int[] nums) {
//版本三:在向集合中添加元素的时候就判断
Set<Integer> set = new HashSet<Integer>();
for(int i : nums){
//添加失败,说明当前要添加的元素已经存在
if(!set.add(i)){
return true;
}
}
return false;
}
}
通过Set的add方法源码可以看出,返回值为布尔类型,所以如果当前要添加的元素已经存在于集合中,就会导致添加失败,返回 false。
五、只出现一次的数
- 题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
- 解题思路:
方案一:哈希表查重【效率较低】
class Solution {
public int singleNumber(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
for(int i : nums){
//将元素添加到集合中
if(!set.add(i)){
//如果已经存在,那么就将这个元素删除
set.remove(i);
}
}
//最后将Object类型的集合强转为数组,剩下的唯一的元素就是答案
return (int)set.toArray()[0];
}
}
方案二:分组遍历 【效率中等】
class Solution {
public int singleNumber(int[] nums) {
int index = 0;
int len = nums.length;
//将数组排序
Arrays.sort(nums);
//遍历数组
while(index < len - 2){
//两个一组,如果该组两个数不相同,那么答案就是该组第一个数
if(index < len - 2 && nums[index] == nums[index + 1]){
index += 2;
}else{
break;
}
}
//返回答案
return nums[index];
}
}
方案三:异或运算【效率较高】
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
// a ^ a = 0、 a ^ 0 = a
for(int i : nums){
res ^= i;
}
return res;
}
}
六、两个数组的交集 II
- 问题描述:给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
- 解题思路:
将一个数组存储到哈希表(散列桶)中,key 对应元素,value 对应该元素出现的次数
再遍历另一个数组,如果该数组中元素在哈希表中存在,那么就将该元素添加到结果数组中,并将该元素在哈希表中对应的value减一
最后可以重新创建一个数组获取结果,也可以利用Arrays.copyOfRange()
方法
- 代码部分:
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
//将其中一个数组存储到哈希表中
Map<Integer, Integer> map = new HashMap();
for(int i = 0; i < nums1.length; i++){
map.put(nums1[i], map.getOrDefault(nums1[i], 0) + 1);
}
//创建临时数组与数组索引
int [] res = new int[nums1.length];
int index = -1;
//遍历长数组,将元素添加到结果集中
for(int i : nums2){
if(map.containsKey(i) && map.get(i) > 0){
res[++index] = i;
map.put(i, map.get(i) - 1);
}
}
//创建数组保存实际的结果
// int [] result = new int[index + 1];
// for(int i = 0; i <= index; i++){
// result[i] = res[i];
// }
//返回结果
return Arrays.copyOfRange(res, 0, index + 1);
//return result;
}
}
结尾两种方式的效率是十分接近的。
七、加一
- 题目描述: 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。
- 解题思路:
从最低位开始计算,如果加一后的值小于等于九,那么就更新这个最低位,直接返回原数组
如果产生进位,那么就将该位记为零,并通过循环对其高位再次进行运算,并记录产生进位的次数
如果进位次数小于数组长度,则直接返回原数组,否则就要创建一个新数组返回
- 代码部分:
class Solution {
public int[] plusOne(int[] digits) {
//统计产生多少次进位
int num = 0;
//遍历数组
for(int i = digits.length - 1; i >= 0; i--){
//无进位
if(digits[i] + 1 <= 9){
digits[i]++;
break;
}else{
//产生进位
digits[i] = 0;
num++;
}
}
//原数组大小不够
if(num == digits.length){
int [] res = new int[num + 1];
res[0] = 1;
return res;
}
return digits;
}
}
八、移动零
- 问题描述: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。
- 解题思路:
方案一:双指针,寻找零与非零位置【效率较低】
方案二:双指针,寻找非零位置【效率较高】
☀️ 方案一
class Solution {
public void moveZeroes(int[] nums) {
//只有一个元素
if(nums.length == 1){
return;
}
//设置双指针
int left = 0;
int right = 0;
//遍历数组
while(left < nums.length){
//找到左指针位置(零)
while(left < nums.length && nums[left] != 0){
left++;
}
//找到右指针位置(非零)
right = left + 1;
while(right < nums.length && nums[right] == 0){
right++;
}
//交换值与指针位置
if(right < nums.length){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
}else{
//达到了数组末尾
return;
}
}
}
}
☀️ 方案二
class Solution {
public void moveZeroes(int[] nums) {
//只有一个元素
if(nums.length == 1){
return;
}
//设置双指针
int left = 0;
int right = 0;
//遍历数组
while(right < nums.length){
//右指针寻找非零数,与左指针交换,右指针到达尾部循环结束
if(nums[right] != 0){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
//每交换一次左指针移动一步
left++;
}
right++;
}
}
}
九、两数之和
-
问题描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
-
解题思路:
最容易理解的方式:双层for循环去遍历 【方法一】
优化版本:利用哈希表的key-value
,简化为一次for循环 【方法二】
🐟方法一
class Solution {
public int[] twoSum(int[] nums, int target) {
//双层遍历
for(int i = 0; i < nums.length; i++){
int num = target - nums[i];
for(int j = i + 1; j < nums.length; j++){
if(nums[j] == num){
return new int []{i, j};
}
}
}
return null;
}
}
🐟方法二
class Solution {
public int[] twoSum(int[] nums, int target) {
//利用哈希表,因为需要返回下标,所以得用散列桶
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//遍历数组
for(int i = 0; i < nums.length; i++){
if(map.containsKey(target - nums[i])){
return new int []{i, map.get(target - nums[i])};
}else{
//如果对应结果不存在,就将当前元素添加到哈希表中
map.put(nums[i], i);
}
}
return null;
}
}
十、有效的数独
- 问题描述:
- 解题思路:
设置三个二维数组,分别记录行、列、小矩形中的元素【易知:九行九列九个小矩形 10个元素】
双层for循环确定行和列,那么小矩形的索引如何确定呢?
巧妙运用数学公式:j / 3 + (i / 3) * 3
就可以根据当前行列确定为第几个盒子
- 代码部分:
class Solution {
public boolean isValidSudoku(char[][] board) {
//利用三个二维数组分别记录 行、列、盒子 【9行、9列、9个盒子、10个数】
int [][] row = new int[9][10];
int [][] coloum = new int[9][10];
int [][] box = new int[9][10];
//遍历数组
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
//获取当前位置的数
if(board[i][j] == '.'){
continue;
}
//将字符转化为整数
int num = board[i][j] - '0';
//判断是否满足规则
if(row[i][num] == 1){
return false;
}
if(coloum[j][num] == 1){
return false;
}
int index = j / 3 + (i / 3) * 3;
if(box[index][num] == 1){
return false;
}
//更新该元素在数组中的记录
row[i][num] = 1;
coloum[j][num] = 1;
box[index][num] = 1;
}
}
//返回结果
return true;
}
}
十一、旋转图像
- 问题描述:
- 解题思路:
(1)采用翻转两次,就可以达到旋转图像的目的
(2)也可以采用临时数组保存结果,最后赋值给原数组
💙 方法一
class Solution {
public void rotate(int[][] matrix) {
//先上下翻转,再主对角线翻转
int n = matrix.length;
//上下翻转
for(int i = 0; i < n/2; i++){
for(int j = 0; j < n; j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
//主对角线翻转
for(int i = 0; i < n; i++){
for(int j = i; j < n; j++){
if(i == j){
continue;
}
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}
💚 方法二
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
//临时数组
int [][] temp = new int[n][n];
//遍历数组
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
temp[j][n - i - 1] = matrix[i][j];
}
}
//赋值
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
matrix[i][j] = temp[i][j];
}
}
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
字符串
一、反转字符串
- 问题描述:
- 解题思路:
遍历数组,将前半部分与后半部分的元素进行交换
- 代码部分:
class Solution {
public void reverseString(char[] s) {
//遍历交换
for(int i = 0; i < s.length >> 1; i++){
char temp = s[i];
s[i] = s[s.length - 1 - i];
s[s.length - 1 - i] = temp;
};
}
}
二、整数反转
-
问题描述:
-
解题思路:
(1)应用StringBuffer类的 reverse() 方法,此处利用捕获异常来对溢出进行处理
(2)利用数学方法进行处理
- 代码部分:
☀️ 方法一
class Solution {
public int reverse(int x) {
//将x转化为StringBuffer的对象
StringBuffer sb = x > 0 ? new StringBuffer(x + "") : new StringBuffer(Math.abs(x) + "-");
//反转字符串
sb.reverse();
//保存结果
int ans = 0;
try{
//如果存在溢出就捕获异常,不进行处理返回0
ans = Integer.parseInt(sb.toString());
}catch(Exception e){}
//返回结果
return ans;
}
}
☀️ 方法二
class Solution {
public int reverse(int x) {
//保存临时结果
long ans = 0;
//遍历整数的每一位
while(x != 0){
//从末尾取数,没取一次将原来的结果扩大10倍
ans = ans * 10 + x % 10;
x /= 10;
}
//要求返回的是整数
return (int)ans == ans ? (int)ans : 0;
}
}
三、字符串中的第一个唯一字符
-
问题描述:
-
解题思路:
(1)哈希表存储: 将字符串中的字符与出现次数存储到哈希表中,遍历字符串并查阅哈希表,如果当前字符的Value为1,那么直接返回索引,没找到返回 -1
(2)数组计数: 方法与前面的类似,只是通过数组来记录字母出现的次数
- 代码部分:
💙 方法一
class Solution {
public int firstUniqChar(String s) {
//只有一个字符
if(s.length() == 1){
return 0;
}
//创建哈希表
Map<Character, Integer> map = new HashMap<Character, Integer>();
//遍历字符串,将字符与出现次数的映射存储到哈希表中
for(char c : s.toCharArray()){
map.put(c, map.getOrDefault(c, 0) + 1);
}
//遍历字符串,找到第一个唯一字符
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
if(map.get(ch) == 1){
return i;
}
}
return -1;
}
}
🖤 方法二
class Solution {
public int firstUniqChar(String s) {
//只有一个字符
if(s.length() == 1){
return 0;
}
//数组计数
int [] count = new int[26];
for(char c : s.toCharArray()){
count[c - 'a']++;
}
//遍历字符串
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if(count[c - 'a'] == 1){
return i;
}
}
return -1;
}
}
四、有效的字母异位词
- 问题描述:
- 解题思路:
用两个额外的数组记录对应字母的出现次数,最后再比较一下
- 代码部分:
class Solution {
public boolean isAnagram(String s, String t) {
//两个字符串长度一定要相同
if(s.length() != t.length()){
return false;
}
//创建两个计数数组
int [] arr1 = new int[26];
int [] arr2 = new int[26];
//遍历两个字符串,统计元素出现次数
for(char c : s.toCharArray()){
arr1[c - 'a']++;
}
for(char c : t.toCharArray()){
arr2[c - 'a']++;
}
//对比
for(int i = 0; i < 26; i++){
if(arr1[i] != arr2[i]){
return false;
}
}
return true;
}
}
五、验证回文串
- 问题描述:
- 解题思路:
将字符串中的数字和字母提取出来,利用了StringBuffer类字符串,然后记录当前String类字符串和反转后的字符串,比较他们的内容是否相同,相同返回 true,不同返回 false
- 代码部分:
class Solution {
public boolean isPalindrome(String s) {
//创建一个StringBuffer类的对象
StringBuffer sb = new StringBuffer();
//将字符串中的数字和字母提取出来
for(char c : s.toCharArray()){
if((c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')){
sb.append(c);
}
}
//记录获取到的字符串和反转后的字符串
String s1 = sb.toString().toLowerCase();
String s2 = sb.reverse().toString().toLowerCase();
//比较反转后是否相同
if(s1.equals(s2)){
return true;
}
return false;
}
}
六、字符串转换整数(atoi)
- 问题描述:
- 解题思路:
将字符串转化为字符数组,遍历找到第一个非空元素,通过标记确定正负号,然后继续遍历,如果字符不为0-9,那么直接结束遍历。
如果超过整数范围就返回对应的最大值和最小值,否则通过 **res = res * 10 + flag * (ch[i] - ‘0’);**计算
- 代码部分:
class Solution {
public int myAtoi(String s) {
//利用字符数组存储字符串
char [] ch = s.toCharArray();
//记录索引位置
int index = 0;
int len = s.length();
//找到前面第一个非空格位置
while(index < len && ch[index] == ' '){
index++;
}
//全空格
if(index == len){
return 0;
}
//判断正负号
int flag = 1;
char firstChar = ch[index];
if(firstChar == '+'){
index++;
}else if(firstChar == '-'){
flag = -1;
index++;
}
//保存结果
int res = 0;
//再次遍历添加元素
for(int i = index; i < len; i++){
//因为都是用对应ASCII码记录的,如果不是数组就直接返回
if(ch[i] < '0' || ch[i] > '9'){
break;
}
//判断是否越界
if(res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE/10 && ch[i] - '0' > Integer.MAX_VALUE % 10)){
return Integer.MAX_VALUE;
}
if(res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE/10 && ch[i] - '0' > -(Integer.MIN_VALUE % 10))){
return Integer.MIN_VALUE;
}
res = res * 10 + flag * (ch[i] - '0');
}
return res;
}
}
七、实现strStr()
- 问题描述:
- 解题思路:
利用Java提供的indexOf方法 【实际应该用KMP算法】
- 代码部分:
class Solution {
public int strStr(String haystack, String needle) {
//第一次出现子串的位置
if(needle.length() == 0){
return 0;
}
return haystack.indexOf(needle);
}
}
八、外观数列
- 问题描述:
- 解题思路:
利用StringBuffer存储当前项的字符串,利用res存储前一项的字符串,通过递推方式获得结果
- 代码部分:
class Solution {
public String countAndSay(int n) {
//利用递推的原理
String res = "1";
//从第二项起,获得结果需要递推n - 1次
for(int i = 0; i <= n - 2; i++){
int len = res.length();
StringBuffer sb = new StringBuffer();
//遍历完前一项的字符串
for(int j = 0; j < len; j++){
int count = 1;
while(j < len - 1 && res.charAt(j) == res.charAt(j + 1)){
count++;
j++;
}
//此处取的是res前一项的字符
sb.append(count+"").append(res.charAt(j));
}
//获取第 i + 2 项的字符串
res = sb.toString();
}
//返回结果
return res;
}
}
九、最长公共前缀
-
问题描述:
-
解题思路:
判断有几个字符串,如果只有一个,那么就直接返回这个字符串 strs[0]
存在多个字符串就找字符串的最小长度为多少,空串就代表零
设置标记和存储索引的变量,根据字符串的最小长度去对比每个字符串的对应位置元素,如果出现不同了,就更新索引和标记,然后跳出两层循环
如果索引为零或字符串最小长度为零,则直接返回空串
最后利用substring方法截取字符串作为结果返回【获得字符串的索引范围:0, index - 1】
- 代码部分:
class Solution {
public String longestCommonPrefix(String[] strs) {
//获取字符串数组大小
int len = strs.length;
if(len == 1){
return strs[0];
}
//设置标记
boolean flag = true;
int index = -1;
//获得最小字符串的长度
int minLen = Integer.MAX_VALUE;
for(int i = 0; i < len; i++){
minLen = Math.min(minLen, strs[i] != null ? strs[i].length() : 0);
}
//双层for循环
for(int i = 0; i < minLen; i++){
for(int j = 0; j < len - 1; j++){
if(strs[j].charAt(i) != strs[j + 1].charAt(i)){
index = i;
flag = false;
break;
}
}
if(!flag){
break;
}
}
//判断是否找到公共前缀
if(index == 0 || minLen == 0){
return "";
}
index = index == -1 ? minLen : index;
//因为截取的范围为[0, index)
String res = strs[0].substring(0, index);
return res;
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
链表
一、删除链表中的结点
- 问题描述:
- 解题思路:
提示里面有一点很重要,每个节点的值都是唯一的
按照常规思路,我们要找到它的前驱结点,让后将前驱结点的后继指针指向当前结点的下一个结点
此处因为不能访问头结点,我们可以通过交换结点的值,来相对更换结点的位置,然后再去掉无用结点即可
- 代码部分:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
//链表的各个结点的值是唯一的,交换结点的值就相当于更换了节点的位置
node.val = node.next.val;
//移动指针
node.next = node.next.next;
}
}
二、删除链表的倒数第N个结点
- 问题描述:
- 解题思路:
与正常删除链表中结点的思路类似,重点在于找到待删除结点的前驱结点,这个要删除的结点是不是头结点还要分情况讨论,因为要求返回头结点。
- 代码部分:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//存储结点与对应位置的关系
ListNode [] list = new ListNode[30];
//获取链表头结点
ListNode pre = head;
//记录结点的个数,并充当索引
int index = -1;
while(pre != null){
list[++index] = pre;
pre = pre.next;
}
//删除结点
if(index - n + 1 > 0){
list[index - n].next = list[index - n + 1].next;
}else{
//该节点为头结点
head = head.next;
}
return head;
}
}
三、反转链表
- 问题描述:
- 解题思路:
三次遍历链表,利用数组将各个结点的值存储下来,最后再逆序赋值回去
- 代码部分:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//统计链表结点个数
int count = 0;
//获取头指针
ListNode p = head;
//遍历链表
while(p != null){
count++;
p = p.next;
}
//保存链表结点值的数组
int [] num = new int[count];
//更新p指向表头
p = head;
//将链表的值存储到数组中
for(int i = 0; i < count; i++){
num[i] = p.val;
p = p.next;
}
//更新p指向表头
p = head;
//再次遍历链表
for(int i = count - 1; i >= 0; i--){
p.val = num[i];
p = p.next;
}
return head;
}
}
方法二:创建新链表 【使用了额外的空间】
class Solution {
public ListNode reverseList(ListNode head) {
//同样是通过结点的值入手,只不过是创建一个新的链表
ListNode res = null;
for(ListNode p = head; p != null; p = p.next){
res = new ListNode(p.val, res);
}
return res;
}
}
方法三:更新指针的指向
class Solution {
public ListNode reverseList(ListNode head) {
//如果链表中结点个数不大于1,那么没有更新指针的必要
if(head == null || head.next == null){
return head;
}
//记录相邻两结点
ListNode left = head;
ListNode right = head.next;
//对原来的头结点进行处理
head.next = null;
//遍历链表
while(right != null){
//更新相邻结点的指针
ListNode p = right.next;
right.next = left;
//更新左右指针
left = right;
right = p;
}
//右结点为空,左节点到达原链表的末尾
return left;
}
}
四、合并两个有序链表
- 问题描述:
- 解题思路:
利用两链表中的较小值去创建一个新的链表作为结果返回
- 代码部分:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//判断两个链表是否为空
if(list1 == null){
return list2;
}else if(list2 == null){
return list1;
}
//新链表头结点
ListNode p = list1.val > list2.val ? new ListNode(list2.val,null) : new ListNode(list1.val,null);
ListNode res = p;
//遍历两链表,创建新的结果链表
while(list1 != null && list2 != null){
if(list1.val > list2.val){
//获取新链表的结点
res.next = new ListNode(list2.val);
//更新res
res = res.next;
list2 = list2.next;
}else{
res.next = new ListNode(list1.val);
//更新res
res = res.next;
list1 = list1.next;
}
}
//可能其中一个链表还有剩余部分
while(list1 != null){
res.next = new ListNode(list1.val);
//更新res
res = res.next;
list1 = list1.next;
}
while(list2 != null){
res.next = new ListNode(list2.val);
//更新res
res = res.next;
list2 = list2.next;
}
//返回结果,因为有个链表的头结点用了两次
return p.next;
}
}
五、回文链表
- 问题描述:
- 解题思路:
利用数组来存储链表的所有结点的值,再遍历数组来判断是否构成回文序列
- 代码部分:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
//结点个数不大于1
if(head == null || head.next == null){
return true;
}
//获取链表头结点
ListNode p = head;
int count = 0;
//遍历链表
while(p != null){
count++;
p = p.next;
}
//更新p
p = head;
//利用数组保存链表的值
int [] num = new int[count];
for(int i = 0; i < count; i++){
num[i] = p.val;
p = p.next;
}
//遍历数组判断是否为回文链表
for(int i = 0; i < (count >> 1); i++){
if(num[i] != num[count - 1 -i]){
return false;
}
}
return true;
}
}
六、环形链表
- 问题描述:
- 解题思路:
利用快慢指针,如果链表没有环,那么快指针会先为空;如果链表有环,那么两个指针终究会相遇
那么为什么有环两个指针就一定会相遇呢?
如果有环,两个指针最后都会走到环上,假设环总长为n步,而两个相距的短边为x步,他们每移动一次距离缩小一步,所以当移动x次之后,两指针一定会相遇
- 代码部分:
/**
* 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 low = head;
ListNode fast = head;
//遍历链表
while(fast != null && fast.next != null){
low = low.next;
fast = fast.next.next;
//两指针相遇了
if(low == fast){
return true;
}
}
return false;
}
}
也可以牺牲速度换空间,利用集合来解决,将链表的结点添加到集合中,如果集合中已经存在了当前结点,那么就会添加失败,则证明有环存在
/**
* 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) {
Set set = new HashSet<ListNode>();
while(head != null){
if(!set.add(head)){
return true;
}
head = head.next;
}
return false;
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
树
一、二叉树的最大深度
-
问题描述:
-
解题思路:
采用深度优先搜索的思想,利用自底向上的方式,最大深度为左右子树的最大深度 + 1
- 代码部分:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
//递归左子树的最大深度
int leftMaxDepth = maxDepth(root.left);
//递归右子树的最大深度
int rightMaxDepth = maxDepth(root.right);
//二叉树的最大深度为子树的最大深度加一
return Math.max(leftMaxDepth, rightMaxDepth) + 1;
}
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/9dc5500490784c10bf0e1bf8053bcd1b.png)
二、验证二叉搜索树
- 问题描述:
- 解题思路:
因为二叉搜索树的特殊性质,中序遍历得到的是一个递增的序列,利用集合将结点值存储起来,再遍历集合检查是否严格满足递增序列【因为涉及到根据索引在集合中取值,所以我采用的是List而不是Set】
- 代码部分:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isValidBST(TreeNode root) {
//只有一个结点
if(root.left == null && root.right == null){
return true;
}
//利用集合来存储二叉树结点的值
List<Integer> set = new ArrayList<Integer>();
//中序遍历
inorder(root, set);
//遍历集合
for(int i = 0; i < set.size() - 1; i++){
if(set.get(i) >= set.get(i + 1)){
return false;
}
}
return true;
}
public void inorder(TreeNode root, List<Integer> set){
//当前结点为空
if(root == null){
return;
}
//遍历左子树
inorder(root.left, set);
//添加结点值
set.add(root.val);
//遍历右子树
inorder(root.right, set);
}
}
三、对称二叉树
- 问题描述:
- 解题思路:
至少存在一个结点,所以从左右子树入手,对于左右子树是否为空进行分情况讨论,比较两个子树当前结点是否相同,然后进行递归比较
- 代码部分:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
//只有根结点
if(root.left == null && root.right == null){
return true;
}
//根结点的左子树
TreeNode leftNode = root.left;
TreeNode rightNode = root.right;
//调用比较的方法
return isSymmetry(leftNode, rightNode);
}
public boolean isSymmetry(TreeNode r1, TreeNode r2){
//两者都为空
if(r1 == null && r2 == null){
return true;
}
//两者有一个为空
if(r1 == null || r2 == null){
return false;
}
//比较结点值
if(r1.val != r2.val){
return false;
}
//递归进入子树
boolean leftNode = isSymmetry(r1.left, r2.right);
boolean rightNode = isSymmetry(r2.left, r1.right);
return leftNode && rightNode;
}
}
四、二叉树的层序遍历
- 问题描述:
- 解题思路:
层序遍历采用的是自左向右的顺序,利用当前索引来控制是否创建新的子集合,以及将当前的值添加到第几个子集合中
- 代码部分:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
//根结点为空
if(root == null){
return new ArrayList<List<Integer>>();
}
//创建一个结果集合
List<List<Integer>> res = new ArrayList<List<Integer>>();
//调用遍历方法
level(root, res, 1);
//返回结果
return res;
}
public void level(TreeNode root, List<List<Integer>> res, int index){
//当前结点为空
if(root == null){
return;
}
//判断是否要创建下一层
if(res.size() < index){
res.add(new ArrayList<Integer>());
}
//将当前元素添加到集合中
res.get(index - 1).add(root.val);
//递归左子树
level(root.left, res, index + 1);
//递归右子树
level(root.right, res, index + 1);
}
}
五、将有序数组转化为二叉搜索树
- 问题描述:
- 解题思路:
二叉搜索树中序遍历得到的就是一个升序序列,所以我们不断去取数组中间的元素去构造结点,然后利用左半部分去构造左子树,又半部分去构造右子树,最后返回根结点。
- 代码部分:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
//只有一个结点
if(nums.length == 1){
return new TreeNode(nums[0]);
}
//调用方法去创建平衡的二叉搜索树
return createTree(nums, 0, nums.length - 1);
}
public TreeNode createTree(int [] nums, int l, int r){
//是否超边界
if(l > r){
return null;
}
//取中点
int index = (l + r) / 2;
//创建一个结点
TreeNode root = new TreeNode(nums[index]);
//递归左右子树
root.left = createTree(nums, l, index - 1);
root.right = createTree(nums, index + 1, r);
//返回根结点
return root;
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
排序和搜索
一、合并两个有序数组
- 问题描述:
- 解题思路:
利用一个临时数组保存结果,不断遍历两个数组,有序取出其中的较小值,可能一个数组中有剩余元素未遍历,再将这部分添加到数组的末尾,最后将临时数组的值赋值给nums1即可。
- 代码部分:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//nums2为空
if(n == 0){
return;
}
//创建一个临时数组
int [] num = new int[m + n];
//设置索引
int index = -1;
int left = 0, right = 0;
//遍历两个数组 双层for循环
while(left < m && right < n){
num[++index] = nums1[left] > nums2[right] ? nums2[right++] : nums1[left++];
}
//可能其中一个数组有剩余
while(left < m){
num[++index] = nums1[left++];
}
while(right < n){
num[++index] = nums2[right++];
}
//遍历num数组
for(int i = 0; i < m + n; i++){
nums1[i] = num[i];
}
}
}
二、第一个错误的版本
-
问题描述:
-
解题思路:
序列是有规律的,从第一个错误版本开始之后的版本都是错误的,为了减少接口调用次数,可以选择二分查找,为了防止溢出取中间值时用
mid = left + (right - left) / 2
- 代码部分:
/* The isBadVersion API is defined in the parent class VersionControl.
boolean isBadVersion(int version); */
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
//false 代表没出错, true 代表出错了,所以说我们要找到第一个true
//左右两个指针
int left = 1, right = n;
while(left < right){
int mid = left + (right - left) / 2;
if(isBadVersion(mid)){
right = mid;
}else{
left = mid + 1;
}
}
return right;
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
动态规划
一、爬楼梯
- 问题描述:
- 解题思路:
当大于等于第三层时,当前n层次可以从n - 1 或 n-2过来,所以满足
dp[i] = dp[i - 1] + dp[i - 2]
,dp[1] = 1、dp[2] = 2
为初始值
- 代码部分:
class Solution {
public int climbStairs(int n) {
//保存结果的数组
int [] dp = new int[46];
//初始化
dp[1] = 1;
dp[2] = 2;
//遍历更新
for(int i = 3; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
二、买股票的最佳时机
- 问题描述:
- 解题思路:
假设在最低点买入,在之后的日子里选择一个最高点卖出
- 代码描述:
class Solution {
public int maxProfit(int[] prices) {
//最低价格
int minPrice = Integer.MAX_VALUE;
//最大利润
int maxPro = Integer.MIN_VALUE;
//在最低点买入
for(int i = 0; i < prices.length; i++){
if(prices[i] < minPrice){
minPrice = prices[i];
}
if(prices[i] - minPrice > maxPro){
maxPro = prices[i] - minPrice;
}
}
//返回结果
return maxPro > 0 ? maxPro : 0;
}
}
三、最大子序和
- 问题描述:
- 解题思路:
要找出连续子序列的最大和,可以采用动态规划的方式,要保证子问题无后效性,也就是说之后计算出来的结果不能对之前已经得到的结果产生影响
dp[i] 代表以第i个元素结尾的最大子序和,状态转移方程为与之前的最大正子序列和加和或为当前元素的值
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
初始值就是只有第一个元素时的最大值
dp[0] = nums[0];
- 代码部分:
class Solution {
public int maxSubArray(int[] nums) {
//记录最大值
int maxValue = Integer.MIN_VALUE;
//dp[i] 代表以第i个元素结尾的最大子序和
int [] dp = new int[nums.length];
//完成初始化
dp[0] = nums[0];
//遍历数组
for(int i = 1; i < nums.length; i++){
//因为以nums[i]结尾,所以一定包含nums[i]
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
}
//遍历动态数组,找出最大值
for(int i : dp){
if(i > maxValue){
maxValue = i;
}
}
//返回结果
return maxValue;
}
}
四、打家劫舍
- 问题描述:
- 解题思路:
因为不能偷相邻的房屋,而且要偷的两个房屋之间隔几个也不确定,所以需要利用到二阶动态方程 dp[i][0] dp[i][1] 分别代表第i个房屋没偷与偷了
如果当前房屋没偷,可能前一个房屋偷了,也可能前一个房屋也没偷
dp[i][0] = Math.max(dp[i - 1][1], dp[i - 1][0]);
如果当前房屋偷了,那么前一个房屋肯定没偷
dp[i][1] = dp[i - 1][0] + nums[i];
动态方程的初始状态就是第一个房屋有没有偷
dp[0][0] = 0;
dp[0][1] = nums[0];
- 代码部分:
class Solution {
public int rob(int[] nums) {
//数组只有一个元素
if(nums.length == 1){
return nums[0];
}
//创建动态数组dp[i][j],代表第i家偷了和第i家没偷的最高金额
int [][] dp = new int[nums.length][2];
//初始状态方程
dp[0][0] = 0;
dp[0][1] = nums[0];
//遍历数组
for(int i = 1; i < nums.length; i++){
//状态转移【不能偷相邻的房屋】
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
dp[i][1] = dp[i -1][0] + nums[i];
}
//返回结果
return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]);
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
设计问题
一、打乱数组
- 问题描述:
- 解题思路:
设计一个类,该类有两个方法 reset可以返回传入的数组,shuffle可以返回打乱后的原数组。
打乱数组采用洗牌的方式,为了保证打乱的概率是相同的,采用将当前元素与之后的元素随机交换【包括当前元素】,最后返回新数组即可
- 代码部分:
class Solution {
//获取传入的数组
public int [] nums;
//利用Random类生成随机索引
Random random = new Random();
//利用构造器获得数组
public Solution(int[] nums) {
this.nums = nums;
}
public int[] reset() {
//直接将传入的数组返回
return nums;
}
public int[] shuffle() {
//获取数组长度
int len = nums.length;
//创建一个新的数组
int [] arr = Arrays.copyOf(nums, nums.length);
for(int i = 0; i < len; i++){
int j = i + random.nextInt(len - i);
//交换 i 和 i后面的一个随机元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//返回新数组
return arr;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* int[] param_1 = obj.reset();
* int[] param_2 = obj.shuffle();
*/
二、最小栈
- 问题描述:
- 解题思路:
利用链表来模拟栈,添加元素的时候采用头插法,对于后序查看栈顶元素、删除栈顶元素都很方便,对于栈中的最小值我们可以为链表设置一个记录最小值的元素
- 代码部分:
class MinStack {
//创建链表头结点
Node head;
public MinStack() {
head = new Node();
}
public void push(int val) {
//头插法创建链表
Node cur = new Node(val);
Node headNext = head.next;
head.next = cur;
cur.next = headNext;
//记录当前结点的最小值
if(cur.next == null){
cur.minVal = cur.val;
}else{
cur.minVal = Math.min(cur.val, cur.next.minVal);
}
}
public void pop() {
//删除头结点后的第一个结点
head.next = head.next.next;
}
public int top() {
return head.next.val;
}
public int getMin() {
return head.next.minVal;
}
}
//结点
class Node{
public int val;
public Node next;
public int minVal;
public Node(int val){
this.val = val;
}
public Node(){}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
数学
一、Fizz Buzz
二、计数质数
-
问题描述:给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
-
解题思路:
(1)我们最容易想到的办法就是暴力循环,质数就是除了本身和一之外没有其他因子,但超时了,此处给出代码理解,不提倡使用
(2)埃氏筛算法,将给出的所有的数都认为是质数,然后根据倍数关系筛掉不是质数的数,重点在于边界条件的处理
当 x > 2时,我们只需要从 x*x开始即可,因为小于x方的都被2的倍数筛选掉
- 代码部分:
方法一
class Solution {
public int countPrimes(int n) {
//方法一:暴力遍历 【终究还是超时了】
//计数
int count = 0;
//双层for循环
for(int i = 2; i < n; i++){
if(judgePrime(i)){
count++;
}
}
//返回结果
return count;
}
public boolean judgePrime(int num){
for(int j = 2; j <= Math.sqrt(num); j++){
//不是质数
if(num % j == 0){
return false;
}
}
return true;
}
方法二
class Solution {
public int countPrimes(int n) {
//埃氏筛
//计数
int count = 0;
//布尔型数组记录是否为素数
boolean [] isPrim = new boolean [n];
//先初始化为所有数为素数
Arrays.fill(isPrim, true);
//遍历数组
for(int i = 2; i * i < n; i++){
//筛掉当前的数的倍数
if(isPrim[i]){
//将其倍数置为非质数
for(int j = i * i; j < n; j += i){
isPrim[j] = false;
}
}
}
//遍历isPrim数组
for(int i = 2; i < n; i++){
if(isPrim[i]){
count++;
}
}
//返回结果
return count;
}
}
三、3的幂
-
问题描述:
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x -
解题思路:
(1)利用集合存储一部分3的幂,检测当前输入的值是否在集合中。根据图像可以看出,3的幂增长速度越来越快,幂总是小于其开平方的运算结果【也就是说,如果传入的数为81,那么集合中记录3的0-9次幂就够了】
class Solution {
public boolean isPowerOfThree(int n) {
//三的幂一定可以被三整除
if(n % 3 != 0 && n != 1){
return false;
}
/*
将指定范围内的三的幂存储到集合中w
*/
Set set = new HashSet();
for(int i = 0; i <= Math.sqrt(n); i++){
set.add((int)Math.pow(3, i));
}
if(set.contains(n)){
return true;
}
return false;
}
}
通过运行结果可以看出效率不高
(2) 采用连除的方法,看最后结果是否为1
class Solution {
public boolean isPowerOfThree(int n) {
while(n % 3 == 0 && n != 0){
n /= 3;
}
return n == 1;
}
}
(3)通过判断该数是否为 在整数范围内 3幂运算最大值的约数
class Solution {
public boolean isPowerOfThree(int n) {
return n > 0 && (1162261467 % n == 0);
}
}
四、罗马数字转整数
- 问题描述:
- 解题思路:
方法一: 减去特殊项
将罗马数字与整数之间的映射关系,将特殊项与正常顺序项的差值记录下来,
class Solution {
public int romanToInt(String s) {
//保存结果
int res = 0;
//利用哈希表存储罗马数与数字的对应关系
Map<Character, Integer> map = new HashMap<Character, Integer>();
map.put('I', 1);
map.put('V', 5);
map.put('X', 10);
map.put('L', 50);
map.put('C', 100);
map.put('D', 500);
map.put('M', 1000);
//将字符串转化为字符数组
char [] cur = s.toCharArray();
for(int i = 0; i < s.length(); i++){
res += map.get(cur[i]);
}
//检测原字符串中是否存在那几个特殊的片段
int diff = 0;
if(s.indexOf("IV") > -1 || s.indexOf("IX") > -1){
diff += 2;
}
if(s.indexOf("XL") > -1 || s.indexOf("XC") > -1){
diff += 20;
}
if(s.indexOf("CD") > -1 || s.indexOf("CM") > -1){
diff += 200;
}
//返回结果
return res - diff;
}
}
- 代码部分:
方法二:分类加和
还是利用哈希表存储,只是在进行转化时,直接与其后一位进行对比判断应该加整数,还是加负数
class Solution {
public int romanToInt(String s) {
//保存结果
int res = 0;
//利用哈希表存储罗马数与数字的对应关系
Map<Character, Integer> map = new HashMap<Character, Integer>();
map.put('I', 1);
map.put('V', 5);
map.put('X', 10);
map.put('L', 50);
map.put('C', 100);
map.put('D', 500);
map.put('M', 1000);
//将字符串转化为字符数组
char [] cur = s.toCharArray();
for(int i = 0; i < s.length() - 1; i++){
res += map.get(cur[i]) >= map.get(cur[i+1]) ? map.get(cur[i]) : -map.get(cur[i]);
}
//返回结果【加上末尾的值】
return res + map.get(s.charAt(s.length() - 1));
}
}
---------------------------------------------------------------------✂----------------------------------------------------------------------------------
其他
一、位1的个数
- 问题描述:
- 解题思路:
因为Java没有无符号整数,所以此处不能通过字符串来判断有多少个一
可以利用Java提供的位运算:按位与
偶数(末尾为零)与 1按位与结果为0,奇数(末尾为一) 与 1
通过右移运算符,从后向前记录1的个数
- 代码部分:
public class Solution {
public int hammingWeight(int n) {
//计数
int count = 0;
//右移32次
for(int i = 0; i < 32; i++){
//末尾为1
if((n & 1) == 1){
count++;
}
//右移
n = n >> 1;
}
//返回结果
return count;
}
}
二、汉明距离
- 问题描述:
- 解题思路:
与位一的个数思路类似,也是获取末尾的二进制数,依次比较,如果不同就将计数器加一,通过右移运算来从后向前获取末尾的二进制值
- 代码部分:
class Solution {
public int hammingDistance(int x, int y) {
//计数
int count = 0;
//通过位1的个数来确定有多少个不同的元素
for(int i = 0; i < 32; i++){
if((x & 1) != (y & 1)){
count++;
}
x >>= 1;
y >>= 1;
}
return count;
}
}
三、颠倒二进制位
- 问题描述:
- 解题思路:
从原无符号整数的末尾不断取二进制数,添加到结果的末尾,即可完成翻转
- 代码部分:
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
//保存结果
int res = 0;
//取出给定整数的每一位
for(int i = 0; i < 32; i++){
//将res左移,空出低位
res <<= 1;
//将n的低位通过或运算添加到res中
res |= n & 1;
//更新n的低位
n >>= 1;
}
//返回结果
return res;
}
}
四、杨辉三角
- 问题描述:
- 解题思路:
直接利用两个集合来处理,内层集合记录一层的元素,外层集合记录所有结果
- 代码部分:
class Solution {
public List<List<Integer>> generate(int numRows) {
//创建结果集
List<List<Integer>> res = new ArrayList<List<Integer>>();
//创建杨辉三角函数的序列
for(int i = 0; i < numRows; i++){
//保存一行的集合
List<Integer> temp = new ArrayList<Integer>();
for(int j = 0; j <= i; j++){
//杨辉三角的两边
if(j == 0 || j == i){
temp.add(1);
}else{
//当前元素的值为 前一行同一列 与 前一行前一列 的值加和
temp.add(res.get(i - 1).get(j) + res.get(i - 1).get(j - 1));
}
}
//将每一行添加到集合中
res.add(temp);
}
//将结果添加到集合中
return res;
}
}
五、有效的括号
- 问题描述:
- 解题思路:
利用栈来处理最合适不过了,当遇到左括号时就将其对应的右括号存储到栈中,如果遇到右括号的时候就取出栈顶元素看是否相同,如果相同就证明匹配上了,最后如果栈中为空就返回 true,否则返回 false
补充一点:对于Stack,因为Stack继承了Vector,而Vector是动态数组接口,可以动态扩容和随机访问,所以这与栈的理念是违背的,在Java中Stack显示为弃用状态。
我们一般用双端队列Deque来实现栈
- 代码部分:
class Solution {
/**
利用栈来存储,左括号入栈,右括号出栈对比
*/
public boolean isValid(String s) {
//创建辅助栈
Deque<Character> stack = new ArrayDeque();
//遍历字符串
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if(c == '('){
stack.push(')');
}else if(c == '['){
stack.push(']');
}else if(c == '{'){
stack.push('}');
}else{
if(stack.isEmpty() || c != stack.pop()){
return false;
}
}
}
//字符串有效
return stack.isEmpty() ? true : false;
}
}
六、缺失数字
- 问题描述:
- 解题思路:
将所有元素加和,利用等差公式计算出从1-nums.length 的和值,比较他们的差值就是缺失的数字
- 代码部分:
class Solution {
public int missingNumber(int[] nums) {
//所有元素加和比较差值
int count = 0;
int len = nums.length;
for(int i : nums){
count += i;
}
return len * (len + 1) / 2 - count;
}
}