1.链表反转:将一条单链表反转。
    解法思路1:单链表的节点有一条指针指向下一个节点,最后一个节点指向null,我需要腾出两个变量缓存当前节点和它的next节点,令下一个节点的next指向当前节点(第一个节点的next指向null),不断迭代,直到最后一个节点。
    解法思路2:递归算法。既然能迭代,就能递归。代码如下。

    1. public static ListNode recursion(ListNode head) {
    2. if (head == null || head.next == null) {
    3. return head;
    4. }
    5. ListNode newHead = recursion(head.next);
    6. head.next.next = head;
    7. head.next = null;
    8. return newHead;
    9. }

    2.统计素数个数:给一个正数n,输出小于n的素数的个数。
    解法1:双循环遍历,第一个循环遍历每一个数,第二个循环遍历它的一半以内能不能被这个数整除,最终计数求和。
    解法2:埃氏算法。记录合数的个数,剩下的就是素数的个数。

    1. public static int eratosthenes(int n) {
    2. //对每个数字产生一个标记,false表示素数
    3. boolean[] isPrime = new boolean[n];
    4. int ans = 0;
    5. for (int i = 2; i < n; i++) {
    6. if (!isPrime[i]) {
    7. ans += 1;
    8. //对这个素数求每一个倍数,把它们都标记为合数
    9. for (int j = i * i; j < n; j += i) {
    10. isPrime[j] = true;
    11. }
    12. }
    13. }
    14. return ans;
    15. }

    3.删除排序数组中的重复项:一个有序数组 nums ,原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
    解法思路:由于是有序的,所以重复必然是相邻的元素重复,可以产生两个指针,不断移动快指针,遇到不重复的就接在慢指针后面。

    1. public int removeDuplicates(int[] nums) {
    2. if (nums.length == 0) return 0;
    3. int i = 0;
    4. for (int j = 1; j < nums.length; j++) {
    5. if (nums[j] != nums[i]) {
    6. i++;
    7. nums[i] = nums[j];
    8. }
    9. }
    10. return i + 1;
    11. }

    4.寻找数组中心下标:数组中某一个下标,左右两边的元素之后相等,该下标即为中心索引
    解法思路:先统计出整个数组的总和,然后从第一个元素开始叠加,
    总和递减当前元素,叠加递增当前元素,直到两个值相等
    5.求平方根:给出一个能开出正整数的正整数n,求平方根
    解法1:对0到n做迭代循环,找到一个自己乘以自己等于n的数。
    解法2:二分查找比从一开始迭代更快找到乘方等于n的数字
    解法3:牛顿迭代,递归算法。如果i是n的平方根,则满足i = (i + x / i) / 2,不断递归,直到满足条件。

    1. public static int newton(int x) {
    2. if(x==0) return 0;
    3. return ((int)(sqrts(x,x)));
    4. }
    5. public static double sqrts(double i,int x){
    6. double res = (i + x / i) / 2;
    7. if (res == i) {
    8. return i;
    9. } else {
    10. return sqrts(res,x);
    11. }
    12. }

    6.两数之和:给定一个升序排列的整数数组 numbers ,从数组中找出两个数满足相加之和等于目标数 target 。假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。返回两数的下标值,以数组形式返回。
    解法1:暴力破解,双循环遍历每一个可能组合。
    解法2:哈希表,在哈希表中找target-当前数的key值,如果不存在就存入当前值为key,下标作为value放进去。

    1. public int[] twoSum(int[] nums, int target) {
    2. Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    3. for (int i = 0; i < nums.length; ++i) {
    4. if (map.containsKey(target - nums[i])) {
    5. return new int[]{map.get(target - nums[i]), i};
    6. }
    7. map.put(nums[i], i);
    8. }
    9. return new int[0];
    10. }

    解法3:二分查找,从第一个值开始固定,二分查找另一个配对值,找不到就顺序固定下一个值。

    1. public int[] twoSearch(int[] numbers, int target) {
    2. for (int i = 0; i < numbers.length; ++i) {
    3. int low = i, high = numbers.length -1;
    4. while (low <= high) {
    5. int mid = (high - low) / 2 + low;
    6. if (numbers[mid] == target - numbers[i]) {
    7. return new int[]{i, mid};
    8. } else if (numbers[mid] > target - numbers[i]) {
    9. high = mid - 1;
    10. } else {
    11. low = mid + 1;
    12. }
    13. }
    14. }
    15. }

    解法4:双指针法,头部指针先固定指向数组左侧,尾部指针指向数组右侧,尾部指针向左移动,找到和为target的值。如果出现和小于target的情况,头部指针右移尾部指针重置,继续找。

    1. public int[] twoPoint(int[] numbers, int target) {
    2. int low = 0, high = numbers.length - 1;
    3. while (low < high) {
    4. int sum = numbers[low] + numbers[high];
    5. if (sum == target) {
    6. return new int[]{low + 1, high + 1};
    7. } else if (sum < target) {
    8. ++low;
    9. }
    10. else {
    11. --high;
    12. }
    13. }
    14. return new int[]{-1, -1};
    15. }

    7.斐波那契数列
    解法1:递归算法。
    算法进阶 - 图1

    1. public static int calculate(int num){
    2. if(num == 0 ){
    3. return 0;
    4. }
    5. if(num == 1){
    6. return 1;
    7. }
    8. return calculate(num-1) + calculate(num-2);
    9. }

    解法2:有数据缓存的递归。原理图中的中间结果缓存后能减少大量存储开销。

    1. public static int calculate(int num){
    2. int[] arr = new int[num+1];
    3. return recurse(arr,num);
    4. }
    5. private static int recurse(int[] arr, int num) {
    6. if(num == 0 ){
    7. return 0;
    8. }
    9. if(num == 1){
    10. return 1;
    11. }
    12. if(arr[num] != 0){
    13. return arr[num];
    14. }
    15. arr[num] = recurse(arr,num-1) + recurse(arr,num-2);
    16. return arr[num];
    17. }

    解法3:双指针线性遍历。不断计算前两个数的和,直到算到目标数字。

    1. public static int iterate(int num){
    2. if(num == 0 ){
    3. return 0;
    4. }
    5. if(num == 1){
    6. return 1;
    7. }
    8. int low = 0,high = 1;
    9. for(int i=2; i<= num; i++){
    10. int sum = low + high;
    11. low = high;
    12. high = sum;
    13. }
    14. return high;
    15. }

    8.链表成环:给定一个链表,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达该节点,则链表中存在环。如果链表中存在环,则返回 true ,否则,返回 false 。
    解法1:哈希表。遍历链表并存入Set,如果已有该节点说明成环。
    解法2:双指针法。定义快慢指针,一直向前遍历,如果进入环中,总有一个时刻两者指向同一节点。两个解法时间复杂度都是O(n),但解法2空间复杂度低。

    1. public static boolean hasCycle(ListNode head) {
    2. if (head == null || head.next == null) {
    3. return false;
    4. }
    5. ListNode slow = head;
    6. ListNode fast = head.next;
    7. while (slow != fast) {
    8. if (fast == null || fast.next == null) {
    9. return false;
    10. }
    11. slow = slow.next;
    12. fast = fast.next.next;
    13. }
    14. return true;
    15. }

    9.排列硬币:总共有 n 枚硬币,将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。给定一个数字 n,找出可形成完整阶梯行的总行数。n 是一个非负整数,并且在32位有符号整型的范围内。
    解法1:暴力算法。从1开始累加,直到总和大于n。
    解法2:二分查找。从n/2开始计算所需硬币数,根据与n比大小进行二分查找。
    10.合并两个有序数组:两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
    解法1:先连接数组合并,再排序。
    解法2:双指针法。分别指向两个数组第一个元素,开始比较,小的放进目标数组,并对指针右移。

    1. public void merge(int[] nums1, int m, int[] nums2, int n) {
    2. int [] nums1_copy = new int[m];
    3. System.arraycopy(nums1, 0, nums1_copy, 0, m);//拷贝数组1
    4. int p1 = 0;//指向数组1的拷贝
    5. int p2 = 0;//指向数组2
    6. int p = 0;//指向数组1
    7. //将数组1当成空数组,比较数组1的拷贝和数组2,将较小的放入空数组
    8. while ((p1 < m) && (p2 < n))
    9. nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++];
    10. //数组2和数组1不等长,将多出的元素拷贝
    11. if (p1 < m)
    12. System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
    13. if (p2 < n)
    14. System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
    15. }

    解法3:双指针优化

    1. public void merge(int[] nums1, int m, int[] nums2, int n) {
    2. int p1 = m - 1;
    3. int p2 = n - 1;
    4. int p = m + n - 1;
    5. while ((p1 >= 0) && (p2 >= 0))
    6. nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];
    7. System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
    8. }

    11.柠檬水找零:在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,一次购买一杯。每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。必须给每个顾客正确找零。注意,一开始你手头没有任何零钱。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
    解法:贪心算法。转化为需要找5刀和15刀的情况,分别看池子里有没有这个存量。

    1. public boolean lemonadeChange(int[] bills) {
    2. int five = 0, ten = 0;
    3. for (int bill : bills) {
    4. if (bill == 5) {
    5. five++;
    6. } else if (bill == 10) {
    7. if (five == 0) {
    8. return false;
    9. }
    10. five--;
    11. ten++;
    12. } else {
    13. if (five > 0 && ten > 0) {
    14. five--;
    15. ten--;
    16. } else if (five >= 3) {
    17. five -= 3;
    18. } else {
    19. return false;
    20. }
    21. }
    22. }
    23. return true;
    24. }

    12.三角形最大周长:给定由一些正数(代表长度)组成的数组A,返回由其中三个长度组成的、面积不为零的三角形的最大周长。如果不能形成任何面积不为零的三角形,返回0。
    解法:贪心算法。先对A排倒序,从最左侧取三个数,如果构不成三角形,说明最左侧的数字和其他数字都不能构成三角形,右移一位,直到能构成三角形。
    13.二叉树遍历
    解法1:递归。

    1. //前序遍历的例子
    2. public static void preorder(TreeNode root) {
    3. if (root == null) {
    4. return;
    5. }
    6. System.out.println(root.val);
    7. preorder(root.left);
    8. preorder(root.right);
    9. }

    解法2:利用栈的特性。

    1. //前序
    2. public static void preOrder2(TreeNode head) {
    3. if (head != null) {
    4. Stack<TreeNode> stack = new Stack<TreeNode>();
    5. stack.add(head);
    6. while (!stack.isEmpty()) {
    7. head = stack.pop();
    8. if(head != null){
    9. System.out.println(head.val);
    10. stack.push(head.right);
    11. stack.push(head.left);
    12. }
    13. }
    14. }
    15. }

    解法3:同样也可以用队列,换一下解法2中入栈顺序即可。