文章目录
链表
反转单链表
- 使用栈解决
利用栈先进后出的特点,把链表节点一个个入栈,然后再一个个的出栈,出栈的时候把一个个的节点串成一个新的链表。
/**
* 反转链表
* 栈实现
* push: 向栈添加元素
* pop: 栈顶元素出栈
* peek: 返回栈顶元素,但元素不出栈
*/
public class BM1 {
// 方法1 放入栈中解决,利用先进后出的特点
public ListNode ReversList(ListNode head) {
Stack<ListNode> stack = new Stack<>();
// 放入元素
while (head != null) {
stack.push(head);
head = head.next;
}
if (stack.isEmpty()) {
return null;
}
// 弹栈 取出反转后的头节点
ListNode node = stack.pop();
ListNode targetNode = node;
// 栈中元素全部出栈,重新将反转后的链表连接成一个新的链表
while (!stack.isEmpty()) {
ListNode tempNode = stack.pop();
node.next = tempNode;
node = node.next;
}
// 最后一个节点的next节点为空,否则会构成环
node.next = null;
// 返回头节点
return targetNode;
}
}
/**
* 定义链表
*/
class ListNode {
// 值
int val;
// 下一个node
ListNode next;
// 赋值
ListNode(int x) {
val = x;
}
}
- 两个链表解决
两个链表求解是指把原链表的节点一个个摘掉,每次摘掉的节点都让它成为新链表的头节点,然后更新链表。
public ListNode ReverseList(ListNode head){
// 新链表
ListNode newNodeHead = null;
while (head != null){
// 先将head的下一个节点存起来,下一步访问使用
ListNode tempNode = head.next;
//每次访问的原链表节点都会成为新链表的头结点,
//其实就是把新链表挂到访问的原链表节点的后面就行了
head.next = newNodeHead;
// 更新新链表
newNodeHead = head;
// 重新赋值head,继续访问
head = tempNode;
}
return newNodeHead;
}
-
递归解决
-
先进行递归再进行逻辑处理,从链表的尾端开始往前处理
-
尾递归,先进行逻辑处理再递归。即链表递归的时候,从前往后处理,处理完直接返回递归的结果。效率比上一种好
// 1.先进行递归再进行逻辑处理,从链表的尾端开始往前处理
public ListNode reverseList(ListNode head) {
// 终止条件
if (head == null || head.next == null) {
return head;
}
// 头节点的下一个节点开始递归
ListNode reverseNode = reverseList(head.next);
// 逻辑处理,完成链表反转 (转了一圈指回head,完成反转)
head.next.next = head;
// head变为了尾节点,尾节点的下一个节点为null,否则会形成环
head.next = null;
// 返回反转后的头节点
return reverseNode;
}
// 2.尾递归,先进行逻辑处理再递归。即链表递归的时候,从前往后处理,处理完直接返回递归的结果。
// 效率比上一种好
public ListNode reverse2(ListNode head) {
return reverse2(head, null);
}
public ListNode reverse2(ListNode head, ListNode newNodeHead) {
if (head == null) {
return newNodeHead;
}
// 头节点的下一个节点
ListNode nextNode = head.next;
// 反转
head.next = newNodeHead;
return reverse2(nextNode, head);
}
两数之和
- for循环暴力求解
- HashMap
public class Solution {
public static void main(String[] args) {
int[] res = twoSum2(new int[]{1, 2, 3, 4, 5, 6, 70}, 75);
System.out.println(Arrays.toString(res));
}
// 1. for
// 时间复杂度:O(n^2) 遍历两次数组
// 空间复杂度:O(1) 未申请额外空间
public static int[] twoSum(int[] num, int target) {
int len = num.length;
for (int i = 0; i < len; i++) {
for (int j = i + 1; j < len; j++) {
if (target - num[i] == num[j]) {
return new int[]{i, j};
}
}
}
return new int[0];
}
// 2. map
// 时间复杂度:O(n) 一次遍历
// hash索引查找的时间复杂度:O(1)
// 空间复杂度:O(n) 申请了n大小的map空间
public static int[] twoSum2(int[] num, int target) {
// key: 数组中的值 value: 值的下标
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < num.length; i++) {
if (map.containsKey(target - num[i]) && map.get(target - num[i]) != i) {
return new int[]{map.get(target - num[i]), i};
}
map.put(num[i], i);
}
return new int[0];
}
}
两个栈实现队列
- 用两个栈实现队列
队列先进先出,栈先进后出,两个栈互助实现队列
/**
* 用两个栈实现队列
* 队列先进先出,栈先进后出,两个栈互助实现队列
*/
public class QueueByStack {
// 入
Stack<Integer> stack1 = new Stack<>();
// 出
Stack<Integer> stack2 = new Stack<>();
// 入
public void push(int value) {
stack1.push(value);
}
// 出
public int pop() {
// stack2有元素直接删除,无元素再放入
if (stack2.isEmpty())
// stack1顺序出栈,并顺序入栈至stack2
while (!stack1.isEmpty())
stack2.push(stack1.pop());
return stack2.pop();
}
}
- 两个队列实现栈
一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素
两个队列始终保持只有一个队列是有数据的
/**
* 两个队列实现栈
* 一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素
* 两个队列始终保持只有一个队列是有数据的
*/
public class StackByQueue<T> {
Queue<T> queue1 = new LinkedList<>();
Queue<T> queue2 = new LinkedList<>();
// 入
public boolean push(T t) {
if (!queue1.isEmpty()) {
return queue1.offer(t);
} else {
return queue2.offer(t);
}
}
// 出
public T pop() {
if (queue1.isEmpty() && queue2.isEmpty()) {
throw new RuntimeException("queue is empty");
}
if (!queue1.isEmpty() && queue2.isEmpty()) {
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
// queue1只剩最后一个
return queue1.poll();
}
if (!queue2.isEmpty() && queue1.isEmpty()) {
while (queue2.size() > 1) {
queue1.offer(queue2.poll());
}
// 最后一个
return queue2.poll();
}
return null;
}
}
动态规划
DP 记住过往
动态规划常常适用于有重叠子问题和最优子结构性质的问题。
简单来说,动态规划其实就是,给定一个问题,我们把它拆分成子问题,直到子问题可以直接解决。然后把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题的一种方法。
一般这些子问题很相似,可以通过函数关系式递推出来。然后呢,动态规划就致力于解决每个子问题一次,减少重复计算,比如斐波那契数列就可以看做入门级的经典动态规划问题。
动态规划核心思想: 拆分子问题、记住过往,减少重复计算。自底向上。
动态规划的解题思路: 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以使用动态规划;
比如一些求最值的场景,如 最长递增子序列、最小编辑距离、背包问题、凑零钱等等,都是动态规划的经典应用场景。
- 穷举分析;
- 确定边界;
- 找出规律,确定最优子结构;
- 写出状态转移方程;
不同的子序列
给定一个字符串 s 和一个字符串 t,计算在 s 的子序列中 t 出现的个数。
即:字符串s 的所有子序列中,和字符串t 完全一样的有多少个
思路:
动态规划解决
三个步骤,定义状态、列出递推公式、找出边界条件。
public int numDistinct(String s, String t) {
int tLength = t.length();
int sLength = s.length();
// 定义dp[i][j]表示 t的前i个字符 可以由 s的前j个字符 组成的个数
// 可以说是 字符串s的前j个字符 组成的子序列中,和 字符串t的前i个字符 组成的字符串一样的有多少个
// 最终我们只需要求出 dp[tLength][sLength] 即可(其中 tLength 和 sLength 分别表示字符串t和s的长度)。
int[][] dp = new int[tLength + 1][sLength + 1];
// base case 边界条件,当t为空串时,空串为任何字符串的子串
for (int j = 0; j <= sLength; j++) {
dp[0][j] = 1;
}
for (int i = 1; i <= tLength; i++) {
for (int j = 1; j < sLength; j++) {
if (t.charAt(i - 1) == s.charAt(j - 1)) {
// 当前位置 t串的字符 与 s串的字符相同
// 两种情况,上一步的记录 + 本次
// 本次 i==j,所以s和t去除末尾相同的字符,取dp[i-1][j-1]
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
} else {
// 当前位置 t串的字符 与 s串的字符相同
dp[i][j] = dp[i][j - 1];
}
}
}
// dp[t.length()][s.length()] 即为 字符串s的子串中 t串 出现的次数
return dp[t.length()][s.length()];
}
数组
删除排序数据中的重复项
题目: 给定一个升序数组,原地
删除重复出现的元素,使得每个元素只出现一次,返回删除后数据的长度,元素的相对顺序要求保持一致
思路: 数组、双指针
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
if(n == 0){
return 0;
}
int j = 1;
for(int i = 1; i < n; i++){
if(nums[i] != nums[i - 1]){
// 先赋值i,再执行 +1 的操作
// 即先给num[j]位置赋值完后,再执行 +1 的操作
nums[j++] = nums[i];
}
}
return j;
}
}
时间 O(n):n为数组长度,快指针和慢指针最多各移动n次
空间 O(1)
关于 i++ 和 ++i
i++ :先赋值,再执行 +1的操作
int i = 1;
int n = i++;
sout(n); // 1 i在执行 +1前赋值给n,所以输出1
sout(i); // 2 i++ 后 i的值变为2,输出2
买卖股票的最佳时机II
题目: 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。
思路: 数组、贪心算法、动态规划
- 动态规划
/**
* 动态规划
* @param prices prices[i]股票第i天的价格
* @return
*/
public int maxProfit(int[] prices) {
int n = prices.length;
// dp 一天内有两种情况:持有股票和卖出股票
// 定义dp表示当天交易结束后的最大利润
int[][] dp = new int[n][2];
// 第一天交易结束后,手里没有股票 0 手里有股票 1
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], prices[i] + dp[i - 1][1]);
dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
}
// 最后一天股票 交易后手里无股票(股票卖出)时,为最大利润
return dp[n - 1][0];
}
时间 O(n): n为数组长度,共 2n个状态,每个状态转移的时间复杂度为 O(1),因此时间复杂度为 O(2n) = O(n)
空间 O(n)
动态规划 空间优化
public static int maxProfit2(int[] prices) {
int n = prices.length;
int dp0 = 0, dp1 = -prices[0];
for (int i = 1; i < n; i++) {
int newDp0 = Math.max(dp0, dp1 + prices[i]);
int newDp1 = Math.max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
}
时间 O(n)
空间 O(1)
- 贪心算法
public int maxProfit(int[] prices) {
// 贪心
// 最大利润只需 将所有的区间(后一天价格大于前一天)的和加起来即为最大利润
// 计算的过程并不是实际的交易过程
int l = prices.length;
int sum = 0;
for(int i = 1; i < l; i++){
if(prices[i] > prices[i - 1]){
sum += prices[i] - prices[i - 1];
}
}
return sum;
}
时间 O(n):遍历一次数组,数组长度为n
空间 O(1):常数空间存放变量
旋转数组
题目: 给定一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数
思路:
- 使用额外数组, 将原数组 i 位置元素放至新数组 (i + k) % k 的位置上,最后进行数组复制
public static void rotate(int[] nums, int k) {
int n = nums.length;
// (i + k) % n
int[] newNums = new int[n];
for (int i = 0; i < n; i++){
newNums[(i + k) % n] = nums[i];
}
// 旋转后的新数组复制至原数组
System.arraycopy(newNums, 0, nums, 0, n);
}
时间 O(n):for 遍历数组, 数组长度为n
空间 O(n)
- 环形替换
思路看懂了,代码没看懂 略
- 数组翻转
public void rotate2(int[] nums, int k) {
int n = nums.length;
k %= nums.length;
// 1.全部反转
reverse(nums, 0, n - 1);
// 2.反转前部分 (第一步反转时,已将尾部的元素移动了一次)
reverse(nums, 0, k - 1);
// 3.反转后半部分
reverse(nums, k, n - 1);
}
/**
* 数组反转
*/
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
时间 O(n):每个元素被翻转两次,一共n个元素
空间 O(1)
存在重复元素
题目: 给定数组,存在重复元素返回true,否则false
思路: 排序后比较相邻元素、stream去重、hashset
- 排序后比较相邻元素
/**
* 排序、判断相邻两个元素是否相同
* @param nums 数组
* @return
*/
public boolean containsDuplicate(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
for (int i = 1; i < n; i++) {
if (nums[i] == nums[i - 1]) {
return true;
}
}
return false;
}
- stream
/**
* stream
* @param nums
* @return
*/
public boolean containsDuplicate2(int[] nums) {
int n = nums.length;
return Arrays.stream(nums).distinct().count() < n;
}
- hashset
/**
* 哈希表
* @param nums
* @return
*/
public boolean containsDuplicate3(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int n : nums) {
// hashset元素不能重复
if (!set.add(n)) {
return true;
}
}
return false;
}
只出现一次的数字
题目: 数组中只有一个元素出现一次,其余元素出现两次,找出出现一次的元素
思路:
- 借用list,如果集合不存在则存入元素,存在则删除,最终list只剩下一个元素,即只出现一次的元素
/**
* list
* @param nums
* @return
*/
public static int singleNumber(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int num : nums) {
if (list.contains(num)) {
list.remove(Integer.valueOf(num));
} else {
list.add(num);
}
}
return list.get(0);
}
- map,存储数字和出现的次数,最终遍历map取出出现1次的key
/**
* map k:数字 v:出现的次数
* @param nums
* @return
*/
public static int singleNumber2(int[] nums) {
Map<Integer, Integer> map = new HashMap<>(16);
for (int num : nums) {
// 统计数字出现的次数
map.compute(num, (k, v) -> {
if (v == null) {
v = 1;
} else {
++v;
}
return v;
});
}
int r = -1;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() == 1) {
r = entry.getKey();
break;
}
}
return r;
}
- 存储list并求和乘以2(保证不重复存入),减去原数组元素之和乘以2,得到的就是仅出现一次的数字
public static int singleNumber3(int[] nums) {
List<Integer> list = new ArrayList<>();
long sum1 = 0;
for (int num : nums) {
sum1 += num;
if (!list.contains(num)) {
list.add(num);
}
}
long sum2 = list.stream().mapToInt(n -> n).summaryStatistics().getSum() * 2;
Long l = (sum2 - sum1);
return l.intValue();
}
以上三种都需要开辟额外的空间,空间复杂度 O(n)
- 位运算-异或运算
异或运算:
- 两个二进制位相同为0,相异为1
- 任何数和 0 做异或运算,结果是本身 即 a ⊕ 0 = a
- 相同两个数进行异或运算, 结果为0, 即 a ⊕ a = 0。
利用异或的特性,对数组所有数字进行异或运算,运算结果即为出现一次的数字
异或运算 满足交换律 先让所有相同的两个数进行异或运算
/**
* 异或运算
* 相同两个数异或为0 任意的数和 0 异或为本身
* 异或满足交换律 先让所有相同的两个数进行异或
*/
public static int singleNumber4(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
两个数组的交集||
题目: 给定两个数组,以数组形式返回交集,返回结果中元素个数和两个数组中出现的次数一致,若不一致,按照出现次数少的返回。
- 哈希表
使用Map。 首先遍历其中一个数组(建议长度最小的),统计每个数字出现的次数,然后遍历第二个数组,若map中包含元素,则将元素添加至数组并将map中元素的次数减一。
public int[] intersect2(int[] nums1, int[] nums2) {
int n = nums1.length > nums2.length ? nums2.length : nums1.length;
Map<Integer, Integer> map = new HashMap<>(16);
// 统计数组中每个数字出现的个数
for (int num : nums1) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
int[] res = new int[n];
int index = 0;
for (int num : nums2) {
if (map.containsKey(num) && map.get(num) > 0) {
res[index++] = num;
map.put(num, map.get(num) - 1);
}
}
return Arrays.copyOfRange(res, 0, index);
}
- 排序 + 双指针
先对两个数组进行排序,然后采用双指针法
初始时,两个指针分别指向两个数组的头部,每次比较两个指针的数字,若不相等,则将指向较小数的指针移动,若两个数相等,则将该数字添加至新数组,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
/**
* 排序 + 双指针
*/
public int[] intersect3(int[] nums1, int[] nums2) {
// 排序
Arrays.sort(nums1);
Arrays.sort(nums2);
int n1 = nums1.length;
int n2 = nums2.length;
int[] num = new int[Math.min(n1, n2)];
// 双指针,分别指向两个数组的头部 及 index新数组添加元素使用
int index1 = 0, index2 = 0, index = 0;
// 两个数组只要一个的指针遍历结束则结束
while (index1 < n1 && index2 < n2) {
// 选择指向较小的数字的指针进行移动
if (nums1[index1] < nums2[index2]) {
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
// 两数相等则将数字放入新数组,然后两个指针同时向后移动一位
num[index++] = nums1[index1];
index1++;
index2++;
}
}
return Arrays.copyOfRange(num, 0, index);
}
数组加一
题目: 给定一个由 整数 组成的 非空数组 所表示的非负整数,在该数基础上加一,返回加一后的数组。
即:将给定数组构成的整数加1后返回新数组
思路: 从最后一位往前遍历数组,判断数字是否为9,若数字为9则将其置为0,且前一位非9的数字加1后 返回
/**
* 倒序遍历,以数字是否为9做处理
* @param digits
* @return
*/
public int[] plusOne1(int[] digits) {
int n = digits.length;
for (int i = n - 1; i >= 0; i--) {
if (digits[i] == 9) {
digits[i] = 0;
} else {
digits[i] += 1;
return digits;
}
}
// 若传入的数组中只含有数字9,则返回数组的第一位元素为1
// 其余未赋值的位置则为默认值0
// 9->10 99->100 999->1000
int[] nums = new int[n + 1];
nums[0] = 1;
return nums;
}
移动零
题目: 给定一个数组 nums,编写函数使得所有 0 移动至末尾,同时保持非零元素的相对顺序,(必须在不复制数组的情况下原地对数组进行操作)
思路: 记录非0数字个数index同时移动元素,并把剩余元素全赋值为0。
/**
* 记录非0数字个数index同时移动元素,并把剩余元素全赋值为0
*/
public static void moveZeroes2(int[] nums) {
int index = 0;
for (int num : nums) {
if (num != 0) {
nums[index++] = num;
}
}
for (int i = index; i < nums.length; i++) {
nums[i] = 0;
}
}
两数之和
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>(16);
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
有效的数独
题目: 判断一个 9*9 的数独是否有效,规则:
- 数字 1 - 9 在每一行只能出现一次
- 1 - 9 在每一列只出现一次
- 1- 9 在每一个 3*3 的宫内只能出现一次
详见LC
思路:
- 普通方法:分别检查每一行、每一列、每个3*3方阵
public boolean isValidSudoku(char[][] board) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
if (board[i][j] != '.' && !checkSudoku(board, i, j)) {
return false;
}
}
}
return true;
}
private boolean checkSudoku(char[][] board, int i, int j) {
// 检查每一行
for (int k = 0; k < board[i].length; k++) {
if (board[i][k] == board[i][j] && k != j) {
return false;
}
}
// 检查每一列
for (int k = 0; k < board.length; k++) {
if (board[k][j] == board[i][j] && k != i) {
return false;
}
}
// 检查每个 3*3 方阵
int m = i / 3 * 3;
int n = j / 3 * 3;
for (int k1 = m; k1 < m + 3; k1++) {
for (int k2 = n; k2 < n + 3; k2++) {
if (board[k1][k2] == board[i][j] && k1 != i && k2 != j) {
return false;
}
}
}
return true;
}
- 哈希表记录数字出现次数
/**
* 哈希表法,记录每行、每列、每个方阵数字出现的次数,判断是否大于1
* 由于数独中数字范围1-9,因此可以用数组代替哈希表
*/
public boolean isValidSudoku2(char[][] board) {
// 行列数-数字 记录每个数字出现的个数
int[][] rows = new int[9][9];
int[][] columns = new int[9][9];
int[][][] boxes = new int[3][3][9];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
char c = board[i][j];
if (c != '.') {
// 将数字转换为数组下标
int index = c - '1';
rows[i][index]++;
columns[j][index]++;
boxes[i / 3][j / 3][index]++;
if (rows[i][index] > 1 || columns[j][index] > 1 || boxes[i / 3][j / 3][index] > 1) {
return false;
}
}
}
}
return true;
}
旋转图像
题目: 给定一个 n * n 的二维矩阵 matrix 表示一个矩阵。将图像原地旋转90度,必须原地旋转,即直接修改输入的二维矩阵。
解:
- 根据矩形的性质,旋转90度。相当于,矩形元素先进行上下交换,再按照主对角线交换(左上-右下)
即:元素交换代替旋转
/**
* 利用矩阵的特性,旋转90度相当于
* 先上下交换,再主对角线(左上-右下)交换
*/
public void rotate1(int[][] matrix) {
int length = matrix.length;
// 上下交换
for (int i = 0; i < length / 2; i++) {
int[] temp = matrix[i];
matrix[i] = matrix[length - 1 - i];
matrix[length - 1 - i] = temp;
}
// 主对角线交换
for (int i = 0; i < length; i++) {
for (int j = i + 1; j < length; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
- 原地一层层旋转
略
字符串
反转字符串
/**
* 数组反转
*/
public void reverseString(char[] s) {
int n = s.length;
for (int i = 0; i < n / 2; i++) {
char temp = s[i];
s[i] = s[n - 1 - i];
s[n - 1 - i] = temp;
}
}
public void reverse2(char[] s) {
int n = s.length;
for (int left = 0, right = n - 1; left < right; ++left, --right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
}
}
整数反转
将传入的整数反转,反转后的数字若超出int的范围,则返回0
- 借用StringBuilder的revers方法;
/**
* 字符串反转 reverse
* String -> Integer 会自动将开头的数字0去掉
*/
public static int reverse1(int x) {
// 判断正负数
int op = x > 0 ? 1 : -1;
// 绝对值
x = Math.abs(x);
String s = new StringBuilder(String.valueOf(x)).reverse().toString();
String sMax = String.valueOf(Integer.MAX_VALUE);
int maxLength = sMax.length();
if (s.length() > maxLength && s.compareTo(sMax) > 0) {
return 0;
}
return Integer.valueOf(s) * op;
}
- 采用数学方法,除法和取余
/**
* 数学
* 反转后的数字超过 int (-2^31, 2^31 - 1)范围时,返回0
* 这里借用 数字类型转换前后是否相等来判断是否超出范围 long -> int
*/
public static int reverse2(int x) {
long res = 0;
while (x != 0) {
res = res * 10 + x % 10;
x /= 10;
}
return (int) res == res ? (int) res : 0;
}
字符串中的第一个唯一字符
找出字符串中的第一个唯一字符,返回索引下标
- 判断字符串第一个和最后一次出现的位置
/**
* 判断字符第一次和最后一次出现位置
*/
public int firstUniqChar(String s) {
for (int i = 0; i < s.length(); i++) {
if (s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i))) {
return i;
}
}
return -1;
}
- hashmap记录出现次数
/**
* 使用hashmap记录出现次数
*/
public int firstUniqChar2(String s) {
char[] chars = s.toCharArray();
Map<Character, Integer> map = new HashMap<>(16);
for (char ch : chars) {
map.put(ch, map.getOrDefault(ch, 0) + 1);
}
for (int i = 0; i < s.length(); i++) {
if (map.get(chars[i]) == 1) {
return i;
}
}
return -1;
}