1、求1+2+…+n

1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

示例 1:

输入: n = 3
输出: 6

示例 2:

输入: n = 9
输出: 45

解题思路

不使用for、if,使用递归进行相加,当n=10

10 +sumNums(10-1)
        9 + sumNums(9-1)
               8 + sumNums(8-1)
                            +到1就停止递归,即(10 +9+... +2 +1)
class Solution {
    public int sumNums(int n) {
        if(n == 1) {
            return 1;
        }
        return n +sumNums(n-1);
    }
}

但是不允许使用if,可以使用&&替代

代码

class Solution {
    public int sumNums(int n) {
        int sumOr1 = n;
        //n > 1就能继续递归,否则终止递归,返回sumOr1(此时sumOr1 = n = 1)
        boolean flag = n>1 && (sumOr1 += sumNums(n-1)) >0; 
        return sumOr1;
    }
}

2、数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

解题思路

一、map

直接使用map,key为数字,value为出现的次数,遍历数组,如果map存在则value+1,否则添加,value为1。

     HashMap<Integer, Integer> map = new HashMap<>();
     int currentNum = 0;
     int currentCount = 0;
     // 存入map
     for(int i = 0; i < nums.length; i++) &#123;
         // 存在
         currentNum = nums[i];
         if(map.get(currentNum) != null) &#123;
             map.put(currentNum, map.get(currentNum) +1 ); // 数量加一
         &#125; else &#123;
             map.put(currentNum, 1);
         &#125;
     &#125;
     Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
     for (Map.Entry<Integer, Integer> entry : entries) &#123;
         if(entry.getValue() == 1) &#123;
             return entry.getKey();
         &#125;
     &#125;
//        Set<Integer> keySet = map.keySet();
//        for (Integer key : keySet) &#123;
//            currentCount = map.get(key);
//            if(currentCount == 1) &#123;
//                return key;
//            &#125;
//        &#125;

二、数组计算各二进制位和

数字的各个二进制位相加%3就会多出唯一的数字的当前位的值

比如 1 +1 +1 +2,二进制位为:

0001 
0001
0001
0010
各个位相加结果:0 0 1 3,
各个位%3即为多出的数的二进制位: 0 0 1 0  => 2
  1. 获取每一位:num & 1 即可获取当前最低位,通过循环num = num >>>1无符号右移,配合num & 1可以逐个获取所有二进制位,并与二进制数组进行相加

  2. 恢复二进制位: 0 0 1 3 => 0 0 1 0 => 2 从二进制位数组最高位开始,sum = sum<<1 + (currentBinary%3)即可

int[] sumArr = new int[32];
int currentNum;
for(int i = 0; i < nums.length; i++ ) &#123;
    currentNum = nums[i];
    for(int j = 31; j >=0; j--) &#123;
        sumArr[j] += currentNum & 1; // 获取最后一位,并添加到对应位数
        currentNum = currentNum >>> 1; // 右移一位
    &#125;
&#125;
int realNum = 0;
// 恢复十进制
for(int i = 0; i <32 ; i++ ) &#123;
    realNum = realNum << 1;
    realNum += sumArr[i]%3; // 结果只可能是1或者0
&#125;
return realNum;

三、有限状态机

在第二种方法我们知道,各个数二进制位的和%3即可得到所求数的当前位数。

每个位1出现的次数为0、1、2,当出现次数为3次时,变为0,循环后,为1的二进制位一定是要求的数的每一位(因为每一位3次重复就为0),如何实现呢。

因为每个二进制位有三种状态,重复0、1、2次,可以使用00,01,10记录三种状态,高位为1表示重复两次,低位为1表示重复一次,重复三次变为00,令a为高位,b为低位,c为当前要测试的数组中的数。

数字逻辑电路学的逻辑函数化简。

~为取反

newA = a~b~c + ~abc

newB = ~ab~c + ~a~bc = ~a(b~c + ~bc) = ~a(b^c) 其中b~c + ~bc为异或

循环后得到的newB即为所求结果(因为低位b为只重复一次的数)

代码

class Solution &#123;
    public int singleNumber(int[] nums) &#123;
        // 3.状态机 0 1 2
        int a = 0, b = 0, preA;
        for(int num : nums) &#123;
            preA = a;
            a = (a & ~b & ~num) | (~a & b & num);
            b = ~preA & ( b ^ num );
        &#125;
        return b;
    &#125;
&#125;

3、链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.
/**
 * Definition for singly-linked list.
 * public class ListNode &#123;
 *     int val;
 *     ListNode next;
 *     ListNode(int x) &#123; val = x; &#125;
 * &#125;
 */

第一种

存入LinkedList,根据size直接get取即可

LinkedList<ListNode> list = new LinkedList<>();
ListNode currentNode = head;
while(currentNode != null) &#123;        // 当前节点不为空
    list.add(currentNode);          // 将当前的node添加进list
    currentNode = currentNode.next; // 跳到下一个节点
&#125;
return list.get(list.size() - k);   // 获取倒数第k个

第二种

直接计算长度,再遍历到所求节点。

int length = 0;
ListNode currentNode;
currentNode = head;
while(currentNode != null) &#123;
    length++;
    currentNode = currentNode.next;
&#125;
currentNode = head;
// System.out.print(currentNode);
for(int i = 0; i < length -k; i++) &#123;
    currentNode = currentNode.next;
&#125;
return currentNode;

第三种

双指针,两指针相差k-1个;

ListNode demandNode = head;
ListNode currentNode = head;
int countStep = 0;
// 开始遍历节点
while(currentNode != null) &#123;
    countStep++;
    if(countStep == k) &#123; // k=1,不相差
        demandNode = head;
    &#125;
    currentNode = currentNode.next; // 跳到下一个
    if(countStep > k) &#123;
        demandNode = demandNode.next; // 目标指针跳下一个
    &#125;
&#125;
return demandNode;

class Solution &#123;
    public ListNode getKthFromEnd(ListNode head, int k) &#123;
        ListNode demandNode = head;
        ListNode currentNode = head;
        int countStep = 1;
        // 将当前指针摆到对应位置
        while(countStep != k) &#123; // k倒数第一个,指针应该同位
            currentNode = currentNode.next;
            countStep++;
        &#125;
        // 两指针同进退
        while(currentNode.next != null) &#123;
            currentNode = currentNode.next;
            demandNode = demandNode.next;
        &#125;
        return demandNode;
    &#125;
&#125;

4、二叉树的深度

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

例如:

给定二叉树 [3,9,20,null,null,15,7],

   3 
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

dfs:深度优先遍历

bfs:层序优先遍历

树的深度,为根节点的左子树深度 与 右子树深度 最大值 +1,递归结束为,节点为空。

/**
 * Definition for a binary tree node.
 * public class TreeNode &#123;
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) &#123; val = x; &#125;
 * &#125;
 */

DFS

后序遍历

class Solution &#123;
    public int maxDepth(TreeNode root) &#123;
        return root == null? 0: Math.max( maxDepth(root.left), maxDepth(root.right) ) +1;
    &#125;
&#125;

BFS

层序优先遍历,一层一层的存入Queue中,根据queue.size获取当前层的节点。

  • Queue
    • poll 删除首部
    • offer 添加到尾部

注意queue.size会实时变化。

public int maxDepth(TreeNode root) &#123;
    Queue<TreeNode> queue = new LinkedList<>(); // 队列
    if(root != null) &#123;
        queue.offer(root); // 添加第一个
    &#125;
    int depth = 0;
    TreeNode currentNode = null;
    while(queue.size() != 0) &#123;
        int size = queue.size();
        for(int i = 0; i < size; i++) &#123;  // 有坑!!!
            currentNode = queue.poll(); // 弹出该节点
            if(currentNode.left != null) &#123;
                queue.offer(currentNode.left);
            &#125;
            if(currentNode.right != null) &#123;
                queue.offer(currentNode.right);
            &#125;
        &#125;
        depth++;
    &#125;
    return depth;
&#125;

5、二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 4

思路

  • 将树dfs遍历存入list,Collections.sort( list )从大到小进行排序,直接获取。
public void getNum(TreeNode treeNode) &#123;
        if(treeNode == null ) &#123;
            return;
        &#125;
        // 右
        if(treeNode.right != null) &#123;
            getNum(treeNode.right);
        &#125;
        // 中
        list.add(treeNode.val);
        // 左
        if(treeNode.left != null) &#123;
            getNum(treeNode.left);
        &#125;
  • 该树是二叉搜索树,通过中序遍历可以按规律排序,使用右、中、左即为从大到小排序,通过全局k–计算已经获取的节点,当等于0时即为要找的节点。
public void getNum(TreeNode treeNode) &#123;
        if(treeNode == null || k == 0) &#123;
            return;
        &#125;
        // 右
        if(treeNode.right != null) &#123;
            getNum(treeNode.right);
        &#125;
        // 中
        k--;
        if(k == 0) &#123;
            needNum = treeNode.val;
        &#125;

        // 左
        if(treeNode.left != null) &#123;
            getNum(treeNode.left);
        &#125;
    &#125;

6、两个栈组成队列

Stack stack = new Stack();

思路: 栈的内容存入另一个栈,取出则是最先的一个。

所以,存入时,只需存入第一个栈即可

取出时,需要遍历栈1存入栈2,栈2pop即可得到最先的一个,再重新存入栈1

注意点:在pop前需要确保size != 0

7、和为s的连续正数序列

https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/mian-shi-ti-57-ii-he-wei-sde-lian-xu-zheng-shu-x-2/

双重循环

一重:1~target

二重:加起来能==target,则存入List中

遍历List存入数组

简单优化

List<int[]>

双重循环

一重:1~target/2

二重:加起来能 == target,则从 x ~ y 存入int中

Arrays.toArray(new int[list.size()][]);

8、二叉树的最近公共祖先

https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-6fdt7/

看不懂

思路:

使用中序遍历,(判空结束)push 中左右 pop,查找出指定节点的路径,缺点是需要用全局变量控制递归的结束

使用for倒叙遍历找出先相同的节点就停止双重循环

实际上只需要从下标0开始,下标一致,找到最后一个不同即可(因为路径是有一段是一样的)。

搜索二叉树

不能用static变量?为啥

简单的左右子树找到路径即可,递归 or for

9、从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

思路:

使用广度优先遍历,使用队列1来保存当前的层级,队列2来保存下一层的层级,使用boolean来控制目前遍历哪个层级,当当前队列为空则需要替换并且添加进入数组。

问题极大

实际上应该使用队列的长度来进行遍历保存!

10、数组中出现次数超过一半的数字

  1. 每一个存入map,超过一半的长度就返回。

  2. 排序后中间的那个

  3. 摩尔投票,多个候选人id在数组中,遍历

    1. 第一个人,写在黑板上,票数为1
    2. 第二个人,同一个人,票数+1,不同人,票数-1

11、数组中重复的数字

  1. 直接使用hashmap
  2. 使用hashset
  3. 一个萝卜一个坑,进行原地置换,

12、和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6

不能两个for

  1. 遍历中,使用二分法查找是否存在另一个补值,但是需要注意4=2+2的情况,如果有两个一样的,二分法只能查找出一个,Arrays.binarySearch(arr, destination)查找不到将返回负数
  2. 使用HashMap更方便
  3. 使用双指针,分别在数组两端,和比taget大则lastIndex–,小则firstIndex++;

13、圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

  • 1 <= n <= 10^5
  • 1 <= m <= 10^6
  1. 使用list、删除指定下标的数据(指针会指向下一个数据,移动数量为 ( nowIndex + 报数限制 - 1 ) % size ),直到还有最后一个,用linklist会超时,因为查找十分需要时间

  2. 正确的不会

14、调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

  1. 直接判断奇偶存到两个list,合并后,使用==list.stream().mapToInt(Integer.intValue).toArray()==;应该学一下流的操作

15、最长回文子串

https://leetcode-cn.com/problems/longest-palindromic-substring/

给你一个字符串 s,找到 s 中最长的回文子串。

暴力破解法

双重for使用substring进行爆破

中心扩展法

每一个字母当做中间,分成odd、even两种

编码时间很长的原因:没想清楚每个变量的用途,没想清楚怎么解题就编码

动态规划

a[i][j]是回文串:

  • 字符串头尾相等
    • AND 依赖于a[i+1][j-1]是回文串(去头和去尾) OR 子串区间长度 j -i >= 3(长度小于2,只需保证头尾相等即可)

16、二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,判断数组中是否含有该整数

一、每行看头尾,是否在数字两侧,在则二分

二、从右上顶点开始,太大则向左,太小则向右

17、二叉搜索树的后序遍历序列

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:

输入: [1,3,2,6,5]
输出: true

根据后序遍历的特点

  • 最后一个肯定是根节点
  • 根据大小划分左右子数,小的都在左边
    • 遍历其他的(划为右子树),必须比根节点大

注意判空,数组边界

优化

二叉搜索树的特点

左子树节点的值 < 根节点的值

右子树的值 > 根节点的值

public boolean verifyPostorder(int[] postorder) &#123;
    return this.verifyPostorder(postorder, 0, postorder.length - 1);
&#125;

private boolean verifyPostorder(int[] postorder, int start, int end) &#123;
    // 任意两个节点都能构成
    if( end - start <= 1) &#123;
        return true;
    &#125;
    int i = start;
    int endVal = postorder[end];
    // 左子树,倒数第二个
    while( postorder[i] < endVal ) &#123; // i < end && 等于的时候就是最后一个了,可以省略
        i++;
    &#125;
    int leftIndex = i;
    // 右子树,倒数第二个
    while( postorder[i] > endVal) &#123;
        i++;
    &#125;
    if(i == end) &#123;
        return verifyPostorder(postorder, start, leftIndex - 1) && verifyPostorder(postorder, leftIndex, end - 1);
    &#125;
    return false;
&#125;

18、包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); –> 返回 -3.
minStack.pop();
minStack.top(); –> 返回 0.
minStack.min(); –> 返回 -2.

一个栈用来正常操作,另一个辅助栈用来min操作

知识点:要知道new Stack<>()的操作,push、pop、peek

辅助栈只需要插入等于或小于其peek的,其他不会影响到min

只需要pop出相等的

private Stack<Integer> A,B;
public MinStack() &#123;
    A = new Stack<>();
    B = new Stack<>();
&#125;
public void push(int x) &#123;
    A.push(x);
    if(B.isEmpty() || B.peek() != null && B.peek() >= x) &#123;
        B.push(x);
    &#125;
&#125;
public void pop() &#123;
    if(A.pop().equals(B.peek())) &#123;
        B.pop();
    &#125;
&#125;