LC AND NC CODE

链表

反转单链表

  1. 使用栈解决

利用栈先进后出的特点,把链表节点一个个入栈,然后再一个个的出栈,出栈的时候把一个个的节点串成一个新的链表。

/**
 * 反转链表
 * 栈实现
 * 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;
  }
}

  1. 两个链表解决

两个链表求解是指把原链表的节点一个个摘掉,每次摘掉的节点都让它成为新链表的头节点,然后更新链表。

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. 递归解决

  2. 先进行递归再进行逻辑处理,从链表的尾端开始往前处理

  3. 尾递归,先进行逻辑处理再递归。即链表递归的时候,从前往后处理,处理完直接返回递归的结果。效率比上一种好

// 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);
}

两数之和

  1. for循环暴力求解
  2. 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];
  }

}

两个栈实现队列

  1. 用两个栈实现队列

队列先进先出,栈先进后出,两个栈互助实现队列

/**
 * 用两个栈实现队列
 * 队列先进先出,栈先进后出,两个栈互助实现队列
 */
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();
  }

}

  1. 两个队列实现栈

一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素

两个队列始终保持只有一个队列是有数据的

/**
 * 两个队列实现栈
 * 一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素
 * 两个队列始终保持只有一个队列是有数据的
 */
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 完全一样的有多少个

思路:

动态规划解决

三个步骤,定义状态、列出递推公式、找出边界条件。

image-20220821225124329
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 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。

思路: 数组、贪心算法、动态规划

  1. 动态规划
   /**
     * 动态规划
     * @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)


  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 是非负数

思路:

  1. 使用额外数组, 将原数组 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)

  1. 环形替换

思路看懂了,代码没看懂 略

  1. 数组翻转
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

  1. 排序后比较相邻元素
	/**
    * 排序、判断相邻两个元素是否相同
    * @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;
   }
  1. stream
	/**
    * stream
    * @param nums
    * @return
    */
   public boolean containsDuplicate2(int[] nums) {
       int n = nums.length;
       return Arrays.stream(nums).distinct().count() < n;
   }
  1. 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;
   }

只出现一次的数字

题目: 数组中只有一个元素出现一次,其余元素出现两次,找出出现一次的元素
思路:

  1. 借用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);
    }
  1. 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;
    }
  1. 存储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)

  1. 位运算-异或运算
    异或运算:
  • 两个二进制位相同为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;
  }

两个数组的交集||

题目: 给定两个数组,以数组形式返回交集,返回结果中元素个数和两个数组中出现的次数一致,若不一致,按照出现次数少的返回。

  1. 哈希表
    使用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);
  }
  1. 排序 + 双指针
    先对两个数组进行排序,然后采用双指针法
    初始时,两个指针分别指向两个数组的头部,每次比较两个指针的数字,若不相等,则将指向较小数的指针移动,若两个数相等,则将该数字添加至新数组,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
	/**
    * 排序 + 双指针
    */
   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
    思路:
  1. 普通方法:分别检查每一行、每一列、每个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
   * 由于数独中数字范围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度,必须原地旋转,即直接修改输入的二维矩阵。
解:

  1. 根据矩形的性质,旋转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;
         }
     }
 }
  1. 原地一层层旋转

字符串

反转字符串

/**
 * 数组反转
 */
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

  1. 借用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;
}
  1. 采用数学方法,除法和取余
 /**
  * 数学
  * 反转后的数字超过 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;
 }

字符串中的第一个唯一字符

找出字符串中的第一个唯一字符,返回索引下标

  1. 判断字符串第一个和最后一次出现的位置
/**
 * 判断字符第一次和最后一次出现位置
 */
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;
}
  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;
}
















图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值