LeetCode算法刷题笔记

2019.06.06 - 第一天

【1.】1描述 : 给出两个整数 a 和 b , 求他们的和。

class Solution {
    /*
     * param a: The first integer
     * param b: The second integer
     * return: The sum of a and b
     */
    public int aplusb(int a, int b) {
        // 主要利用异或运算来完成 
        // 异或运算有一个别名叫做:不进位加法
        // 那么a ^ b就是a和b相加之后,该进位的地方不进位的结果
        // 然后下面考虑哪些地方要进位,自然是a和b里都是1的地方
        // a & b就是a和b里都是1的那些位置,a & b << 1 就是进位
        // 之后的结果。所以:a + b = (a ^ b) + (a & b << 1)
        // 令a' = a ^ b, b' = (a & b) << 1
        // 可以知道,这个过程是在模拟加法的运算过程,进位不可能
        // 一直持续,所以b最终会变为0。因此重复做上述操作就可以
        // 求得a + b的值。
        while (b != 0) {
            int _a = a ^ b;
            int _b = (a & b) << 1;
            a = _a;
            b = _b;
        }
        return a;
    }
};

【2】2描述:设计一个算法,计算出n阶乘中尾部零的个数

https://blog.csdn.net/nwsuaf_uestc/article/details/78788932

点睛之笔:我们发现只有一个数是五的倍数的时候,阶乘的后边就是0,如果一个数可以分解成5*5或者5*10、5*15那么就会结尾添加两个0,也就是说一个数除以五如果大于5那么就会多添加一个0。

public class Solution {
    /*
     * @param n: An integer
     * @return: An integer, denote the number of trailing zeros in n!
     */
    public long trailingZeros(long n) {
        // write your code here, try to do it without arithmetic operators.
        long sum = 0;
        int count = 0;
        
        while(n >= 5){
            n = n / 5;
            sum = sum + n;
            count++;
        }
        return sum;
    }
}

2019.06.08 - 第二天

【3】3统计数字

结论如下:我们可以得到以下规律:

  • 当某一位的数字小于i时,那么该位出现i的次数为:更高位数字x当前位数
  • 当某一位的数字等于i时,那么该位出现i的次数为:更高位数字x当前位数+低位数字+1
  • 当某一位的数字大于i时,那么该位出现i的次数为:(更高位数字+1)x当前位数
    class Solution {
        /*
         * param k : As description.
         * param n : As description.
         * return: An integer denote the count of digit k in 1..n
         */
        public int digitCounts(int k, int n) {
            // write your code here
            int cnt = 0;
            for (int i = k; i <= n; i++) {
                cnt += singleCount(i, k);
            }
            return cnt;
        }
        
        public int singleCount(int i, int k) {
            if (i == 0 && k == 0)
                return 1;
            int cnt = 0;
            while (i > 0) {
                if (i % 10 == k) {
                    cnt++;
                }
                i = i / 10;
            }
            return cnt;
        }
    };
    

    【4】*************5在数组中找到第 k 大的元素。

public class Kth {
    /*
     * @param k : description of k
     * @param nums : array of nums
     * @return: description of return
     */
    public static int kthLargestElement(int k, int[] nums) {
        if (nums == null || nums.length == 0 || k < 1 || k > nums.length){
            return -1;
        }
        return partition(nums, 0, nums.length - 1, nums.length - k);
    }

    private static int partition(int[] nums, int start, int end, int k) {
        if (start >= end) {
            return nums[k];
        }

        int left = start, right = end;
        int pivot = nums[(start + end) / 2];

        while (left <= right) {
            while (left <= right && nums[left] < pivot) {
                left++;
            }
            while (left <= right && nums[right] > pivot) {
                right--;
            }
            if (left <= right) {
                swap(nums, left, right);
                left++;
                right--;
            }
        }

        if (k <= right) {
            return partition(nums, start, right, k);
        }
        if (k >= left) {
            return partition(nums, left, end, k);
        }
        return nums[k];
    }

    private static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    public static void main(String[] args) {
        int k = 1;
        int[] nums = {5,3,7,2,1};
        int res = kthLargestElement(k, nums);
        System.out.println(res);
    }
}

【5】6合并两个有序升序的整数数组A和B变成一个新的数组。新数组也要有序。

public class MergeArray {
    public static void main(String[] args) {
        int[] A = {1,2,3,4};
        int[] B = {2,4,6,8,9,15,34};
        int[] res = merge(A,B);
        for (int re : res) {
            System.out.print(re + ",");
        }
    }

    public static int[] merge(int[] a, int[] b){

        int i = 0, j = 0, index = 0;
        int[] C = new int[a.length + b.length];

        // 这里判断数组都没有完毕的情况
        while (i < a.length && j < b.length){
            // 循环赋值
            if (a[i] < b[j]){
                C[index++] = a[i++];
            }else {
                C[index++] = b[j++];
            }
        }
        //当 a 数组长度没有完结时
        while (i < a.length){
            C[index++] = a[i++];
        }

        //当 b 数组长度没有完结时
        while (j < b.length){
            C[index++] = b[j++];
        }
        return C;
    }
}

【6】7二叉树的序列化与反序列化

【分析1】首先明确序列化与反序列化二叉树。所谓的序列化二叉树就是按照树的某种遍历方式遍历二叉树,将每个结点的值以某种特殊符号(例如,)分隔保存到一个字符串中,如果结点为空,则使用另外一种特殊符号(例如#)表示。反序列化的意思就是根据序列化的字符串重构出该二叉树。这里需要特别说明一点的是,遍历到叶子结点后,还需要继续向下遍历,结点为空,则用特殊字符(例如#)表示。

【分析2】对于二叉树的遍历方式选择上,原则上前、中、后和层次遍历都是可以的。但是为了能够很容易的获取二叉树的根结点,因此我们可以选择前序遍历顺序,这样遍历出来的第一个元素就是二叉树的根结点对应的值。

【分析3】对于二叉树的序列化来说,现在就变成了一个前序遍历二叉树的过程。如果当前结点不为空,则将当前结点对应的值加入到字符串中;如果当前结点为空,则将特殊字符(例如,#)加入到字符串中。

【分析4】根据前序序列化的二叉树字符串,我们需要重构该二叉树。根据前序遍历的特点,我们在获取根结点后,首先应该一直向左构建左子树,直到遇到空结点。如果当前结点为空,则应该返回到它的父结点,然后构造它的父结点的右子树。以此类推,显然这也是一个递归进行的过程。
 

class Solution {
    /**
     * This method will be invoked first, you should design your own algorithm 
     * to serialize a binary tree which denote by a root node to a string which
     * can be easily deserialized by your own "deserialize" method later.
     */
    public String serialize(TreeNode root) {
        if (root == null) {
            return "{}";
        }
        
        // 初始化树
        ArrayList queue = new ArrayList();
        // 将根节点入队列
        queue.add(root);

        // 递归将树节点入队列
        for (int i = 0; i < queue.size(); i++) {
            TreeNode node = queue.get(i);
            if (node == null) {
                continue;
            }
            queue.add(node.left);
            queue.add(node.right);
        }
        // 这一步删除操作有点蒙蔽
        while (queue.get(queue.size() - 1) == null) {
            queue.remove(queue.size() - 1);
        }

        // 将队列元素输出到字符串
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append(queue.get(0).val);
        for (int i = 1; i < queue.size(); i++) {
            if (queue.get(i) == null) {
                sb.append(",#");
            } else {
                sb.append(",");
                sb.append(queue.get(i).val);
            }
        }
        sb.append("}");
        return sb.toString();
    }
    
    /**
     * This method will be invoked second, the argument data is what exactly
     * you serialized at method "serialize", that means the data is not given by
     * system, it's given by your own serialize method. So the format of data is
     * designed by yourself, and deserialize it here as you serialize it in 
     * "serialize" method.
     */
    public TreeNode deserialize(String data) {
        if (data.equals("{}")) {
            return null;
        }
        
        // 去除{}, 逗号分割数据节点
        String[] vals = data.substring(1, data.length() - 1).split(",");
        // 建立树链
        ArrayList queue = new ArrayList();
        // 根节点
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        queue.add(root);
        // 开始遍历
        int index = 0;
        boolean isLeftChild = true;
        for (int i = 1; i < vals.length; i++) {
            // 不管空节点,前序遍历 入队列
            if (!vals[i].equals("#")) {
                TreeNode node = new TreeNode(Integer.parseInt(vals[i]));
                if (isLeftChild) {
                    queue.get(index).left = node;
                } else {
                    queue.get(index).right = node;
                }
                queue.add(node);
            }
            if (!isLeftChild) {
                index++;
            }
            isLeftChild = !isLeftChild;
        }
        return root;
    }
}

【7】8字符串旋转问题

输入:  str="abcdefg", offset = 3
输出:  str = "efgabcd"	
样例解释:  注意是原地旋转,即str旋转后为"efgabcd"
class Solution:
    def rotateString(self, s, offset):
        
        s1 = s[:-offset]
        
        s2 = s[-offset:]
        
        # 直接s = s2 + s1 这样是通不过的
        temp = s2 + s1
        for i in range(len(temp)):
            s[i] = temp[i]
        
        
# class Solution:
#     def rotateString(self, s, offset):
#         # write you code here
#         if len(s) > 0:
#             offset = offset % len(s)
            
#         temp = (s + s)[len(s) - offset : 2 * len(s) - offset]

#         for i in range(len(temp)):
#             s[i] = temp[i]

【8】12. 带最小值操作的栈

使用两个仅支持 pop 和 push 的栈就可以完成, stack 储存压入的数据, minStack 储存最小值.

  • push 直接把元素压入 stack, 对于 minStack, 如果它为空则直接压入, 反之压入当前元素与 minStack 栈顶的最小值
  • pop 两个栈都弹出一个元素, 返回 stack 弹出的元素
  • min 返回 minStack 的栈顶

还可以令 minStack 为单调栈, 即push时只有元素更小的时候才放入这个栈, 而pop时只有栈顶与stack栈顶相同时才弹出

这样可以节约一定的空间, 但是实质上空间复杂度仍然是 O(n), 且多了一些判断, 并不一定更优

public class MinStack {
    private Stack stack;
    private Stack minStack;
    
    public MinStack() {
        stack = new Stack();
        minStack = new Stack();
    }

    public void push(int number) {
        stack.push(number);
        if (minStack.isEmpty()) {
            minStack.push(number);
        } else {
            minStack.push(Math.min(number, minStack.peek()));
        }
    }

    public int pop() {
        minStack.pop();
        return stack.pop();
    }

    public int min() {
        return minStack.peek();
    }
}

 

你可能感兴趣的:(LeetCode算法刷题笔记)