zoukankan      html  css  js  c++  java
  • ARTS习惯(9)

    Algorithm

    每周至少做一个Leetcode算法题

    第1道

    【来源】

    《剑指Offer》12#

    【题目】

    设计一个函数,输入整数n,打印1到最大的n位数

    例子

    输入:3
    输出:1,2,...,998,999
    解释:最大的3位数是999
    

    【解答】

    题目未指定n的大小,需考虑到大数问题,常用的处理大数问题的数据结构是字符串或者数组,本题用字符串来处理。

    解法1:字符串模拟数字加法。

    掌握2个关键步骤问题迎刃而解,1)模拟加1操作,注意处理溢出;2)打印

    解法2:递归实现全排列

    本题等价于n个数字0~9的全排列,用递归很容易实现。基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,打印字符串。

    【示例代码】

    package com.pengluo.hht_offer.T12_PrintNumbers;
    
    public class Print1ToMaxOfN {
    
    
        /**
         * 解法1:字符串模拟数字加法
         * @param n
         */
        public void print1ToMaxOfNBit(int n) {
            if (n <= 0) {
                return;
            }
            // 定义并初始化数组,每个字符的初始值均为'0'
            // 约定:number[0]:最高位,number[n-1]:最低位
            char[] number = new char[n];
            for (int i = 0; i < n; i++) {
                number[i] = '0';
            }
            // 发生溢出时,结束打印
            while (!increment(number)) {
                printNumber1(number);
            }
        }
    
        /**
         *  辅助方法:模拟字符串数字加1
         * @param number
         * @return true表示发生溢出,flase表示正常
         */
        private boolean increment(char[] number) {
            // 溢出标志位
            boolean isOverFlow = false;
            // 进位
            int TakeOver = 0;
            // 每个字符表示的数字
            int Snum =0;
            int length = number.length;
            for (int i = length-1; i >= 0; i--) {
                Snum = number[i] - '0' + TakeOver;
                if (i == length-1) {
                    Snum++;
                }
                // 处理进位逻辑
                if (Snum >= 10) {
                    if (i == 0) {
                        isOverFlow = true;
                    }
                    TakeOver = 1;
                    Snum -= 10;
                    number[i] = (char) ('0' + Snum);
                } else {
                    number[i] = (char) ('0' + Snum);
                    break;
                }
            }
            return isOverFlow;
        }
    
        /**
         * 辅助方法:打印
         * @param number
         */
        private void printNumber1(char[] number) {
            boolean flag = false;
            int length = number.length;
            for (int i = 0; i <length; i++) {
                // 数字左边的0不输出
                if (number[i] != '0') {
                    flag = true;
                }
                if (flag) {
                    System.out.print(number[i]);
                }
    
            }
            System.out.println();
        }
    
        /**
         * 解法2:递归法实现全排列
         *
         * @param n
         */
        public void print1ToMaxNBitByRecursively(int n) {
            if (n <= 0) {
                return;
            }
            char[] number =new char[n];
            for (int i = 0; i < 10; i++) {
                number[0] = (char) (i + '0');
                printByRecursively(number, n, 0);
            }
    
        }
    
        private void printByRecursively(char[] number, int length, int index) {
            // 递归结束条件
            if (index == length - 1) {
                printNumber1(number);
                return;
            }
            for (int i = 0; i < 10; i++) {
                number[index + 1] = (char) (i + '0');
                printByRecursively(number, length, index+1);
            }
        }
    
        /**
         *  错误范例:未统一number表示的数字格式,此方法,默认number[0]为最低位
         * @param number
         */
        private void printNumber(char[] number) {
            boolean flag = false;
            int length = number.length;
            for (int i = length - 1; i >= 0; i--) {
                // 数字左边的0不输出
                if (number[i] != '0') {
                    flag = true;
                }
                if (flag) {
                    System.out.print(number[i]);
                }
    
            }
            System.out.println();
        }
    
    }
    
    

    【拓展】

    • 将本题的打印改成返回一个int[],数组存储所有的打印结果

    • 设计一个函数,可以输出两个数相加的结果。注意输入的参数可能是负数

    总结

    • Int、long 等类型都有表示的数字范围,不能处理大数。需要用String表示大数

    • 解法1是用字符串模拟数字加法运算

    • 解法2是用分治思想,递归实现

    Review

    阅读并点评至少1篇英文技术文章

    【原文】:Head First Java 2nd Edition CH16&core java CH8

    【点评】

    泛型机制是从Java 5开始支持的,专家组花了将近5年时间定义和规范。泛型在集合中应用广泛,ArrayList就是应用最多的集合之一。

    泛型编程的优点是可读性好安全性高代码可重用

    类型参数(type parameters)

    // 添加类型参数:<String>
    // java 7开始,构造函数中的泛型类型可以省略
    ArrayList<String> files = new ArrayList<>();
    

    做一个泛型程序员

    泛型程序员的任务是:预测所用类未来所有可能有的用途。这相当难却很有价值。

    大多数Java程序员满足于API调用

    定义一个简单的泛型类(Generic class)

    // 类型变量:<T>,它指定了方法的返回类型,域、局部变量的类型都为T
    public class Pair<T> {
    
    }
    

    定义一个简单的泛型方法

    public class ArrayAlg{
        
        public static <T> T getMiddle(T[] a) {
            return a[a.length/2];
        }
    }
    

    类型变量的限定

     public class ArrayAlg{
        // 在泛型中,`extends`是`extends` 或`implements`的意思
        // 限定一个
        // 表示只有实现Comparable接口的类可以传入,否则编译报错
        public static <T extends Comparable> T min(T[] a) {
            
        }
         
        // 限定类型用&连接
        public static <T extends Comparable & Serializle> T min(T[] a) {
            
        }
         
         
    }
    

    泛型代码和虚拟机

    JVM里没有泛型代码,全部是普通的类。JVM自动通过类型擦除把泛型类变成原始类型

    类型擦除

    • 用第一个限制类代替泛型变量T,没有明确限制则默认用Object代替T
    // 擦除类型:去掉class上的<T>,域和成员变量T换成对应的类
    // 1) public class Pair<T> {}
    		T-->Object
    // 2)public class <T extends Comparable & Serializle> implements Serializle {}
            T-->Comparable
    // 3)public class <T extends Serializle & Comparable> implements Serializle {}
    		T-->Serializle
    

    JVM自动生成桥方法,用来处理多态

    通配符类型

    如果是一名库程序员,一定要习惯通配符类型

    // 使用通配符,也就是说Employee的所有子类都可以做方法的参数.可以读取(getter)
    public static void printBuddies(Pair<? extends Employee> p)
       {
          Employee first = p.getFirst();
          Employee second = p.getSecond();
          System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
       }
    
    // 超类限制,Manager及其子类可以做方法的参数,可以写入(setter)
    public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
       {
          if (a.length == 0) return;
          Manager min = a[0];
          Manager max = a[0];
          for (int i = 1; i < a.length; i++)
          {
             if (min.getBonus() > a[i].getBonus()) min = a[i];
             if (max.getBonus() < a[i].getBonus()) max = a[i];
          }
          result.setFirst(min);
          result.setSecond(max);
       }
    
    // 无限定通配符
    class PairAlg
    {
       public static boolean hasNulls(Pair<?> p)
       {
          return p.getFirst() == null || p.getSecond() == null;
       }
    
       public static void swap(Pair<?> p) { swapHelper(p); }
    
       public static <T> void swapHelper(Pair<T> p)
       {
          T t = p.getFirst();
          p.setFirst(p.getSecond());
          p.setSecond(t);
       }
    }
    

    Tip

    学习至少一个技术技巧

    缓存穿透、缓存击穿、缓存雪崩的问题由来,解决方法

    缓存穿透:缓存和数据库都没有命中,大部分场景时恶意攻击,大量的请求到达数据库,数据库不堪重负崩了

    解决方案:

    • 接口层做校验,用户鉴权校验
    • 布隆过滤器:将所有存在的数据哈希存到位图中,那么布隆过滤器判断不存在,那么数据库中一定没有,拦截即可。相比直接存到缓存redis中,它的优点是节省空间,比如10亿条数据存到Map等数据结构中,占用900G的空间。大概了解
    • id做基础校验,id<0(非法参数)直接拦截
    • 数据库未命中时,设置短时间的空值缓存,如1分钟。缓解数据库压力

    缓存击穿:缓存中没有,数据库中有。通常是:缓存时间到期,此时有大量并发请求,缓存中取不到数据,数据库瞬间压力很大。

    解决方案:

    • 热点数据永不过期
    • 查询DB过程加互斥锁
      • Redis的SETNX
      • Memcache的ADD
      • ReentrantLock

    缓存雪崩:缓存中数据批量的到期,需要去数据库查询,数据库压力大增。和缓存击穿不同点在,后者是同一key大量并发请求,

    前者这里是很多key的缓存都集体失效

    解决方案:

    • 缓存过期时间设置为随机,避免一个时间同时过期
    • 用分布式,将热点数据均匀存在不同redis数据节点
    • 热点数据永不过期

    Share

    分享一篇有观点和思考的技术文章

    一名资深系统工程师放羊去了

  • 相关阅读:
    C和指针 第十七章 经典数据类型 堆栈 队列 二叉树
    C和指针 第十六章 习题
    C和指针 第十六章 标准函数库 信号
    C和指针 第十六章 标准函数库 本地跳转setjmp.h
    C和指针 第十六章 标准函数库
    C和指针 第十五章 习题
    C和指针 第十五章 二进制I/O
    C和指针 第十五章 文件I/O
    C和指针 第十五章 输入输出缓冲
    C和指针 第十五章 错误报告perror和exit
  • 原文地址:https://www.cnblogs.com/PengLuo22/p/14299900.html
Copyright © 2011-2022 走看看