標籤: 如何寫文案

  • MySQL 性能優化之慢查詢

    MySQL 性能優化之慢查詢

    性能優化的思路

    1. 首先需要使用慢查詢功能,去獲取所有查詢時間比較長的SQL語句
    2. 其次使用explain命令去查詢由問題的SQL的執行計劃(腦補鏈接:點我直達1,點我直達2)
    3. 最後可以使用show profile[s] 查看由問題的SQL的性能使用情況
    4. 優化SQL語句

    介紹

      數據庫查詢快慢是影響項目性能的一大因素,對於數據庫,我們除了要優化SQL,更重要的是得先找到需要優化的SQL語句

      MySQL數據庫有一個“慢查詢日誌”功能,用來記錄查詢時間超過某個設定值的SQL,這將極大程度幫助我們快速定位到問題所在,以便對症下藥

    至於查詢時間的多少才算慢,每個項目、業務都有不同的要求。
        比如傳統企業的軟件允許查詢時間高於某個值,但是把這個標準方在互聯網項目或者訪問量大的網站上,估計就是一個Bug,甚至可能升級為一個功能缺陷。

      MySQL的慢查詢日誌功能,默認是關閉的,需要手動開啟

    開啟慢查詢功能

    查看是否開啟慢查詢功能

     

     

    參數說明:

    • slow_query_log:是否開啟慢查詢,on為開啟,off為關閉;
    • log-slow-queries:舊版(5.6以下版本)MySQL數據庫慢查詢存儲路徑,可以不設置該參數,系統則會給一個缺省的文件:host_name-slow.log
    • long_query_time:慢查詢閥值,當查詢時間多於設置的閥值時,記錄日誌,單位為秒。

    臨時開啟滿查詢功能

      在MySQL執行SQL語句設置,但是如果重啟MySQL的話會失效。

    set global slow_query_log=on;
    set global long_query_time=1;

    永久性開啟慢查詢

      修改:/etc/my.cnf,添加以下內容,然後重啟MySQL服務

    [mysqld]
    lower_case_table_names=1
    slow_query_log=ON
    slow_query_log_file=/usr/local/mysql/data/chenyanbindeMacBook-Pro-slow.log
    long_query_time=1

     

    查看滿查詢啟動狀態

    演示慢查詢

      為了演示方便,我們讓sql睡眠3秒!

    格式說明:

    • 第一行,SQL查詢執行的具體時間
    • 第二行,執行SQL查詢的連接信息,用戶和連接IP
    • 第三行,記錄了一些我們比較有用的信息,
      • Query_timme,這條SQL執行的時間,越長則越慢
      • Lock_time,在MySQL服務器階段(不是在存儲引擎階段)等待表鎖時間
      • Rows_sent,查詢返回的行數
      • Rows_examined,查詢檢查的行數,越長就越浪費時間
    • 第四行,設置時間戳,沒有實際意義,只是和第一行對應執行時間。
    • 第五行,執行的SQL語句記錄信息

    分析滿查詢日誌

    MySQL自帶的mysqldumpslow

     

     

     

    參數說明:

    • -s, 是表示按照何種方式排序,c、t、l、r分別是按照記錄次數、時間、查詢時間、返回的記錄數來排序,ac、at、al、ar,表示相應的倒敘;
    • -t, 是top n的意思,即為返回前面多少條的數據;
    • -g, 後邊可以寫一個正則匹配模式,大小寫不敏感的;

    MySQL性能fenix語句show profile(重要

    介紹

    • Query Profiler是MySQL自帶的一種query診斷分析工具,通過它可以分析出一條SQL語句性能瓶頸在什麼地方。
    • 通常使用explain,以及slow query log都無法做到精確分析,但是Query profiler卻可以定位出一條SQL執行的各種資源消耗情況,比如CPU、IO等,以及該SQL執行所耗費的時間等。不過該工具只有在MySQL5.0.37以上版本中才有實現
    • 默認的情況下,MySQL的該功能沒有打開,需要自己手動打開

    語句使用

    • show profileshow profiles語句可以展示當前會話(退出session后,profiling重置為0)中執行語句的資源使用情況。
    • show profiles:以列表形式显示最近發送到服務器上執行的語句的資源使用情況,显示的記錄數由變量:profiling_history_size控制,默認15條
    • show profile:只是最近一條語句執行的消息資源佔用信息,默認實現Status和Duration兩列

    開啟Profile功能

    • Profile功能由MySQL會話變量:profiling控制,默認是OFF關閉狀態。
    • 查看是否開啟了Profile功能
    select @@profiling;
    
    show variables like '%profil%';

     

    打開profiling功能

    set profiling=1;

     

    show profile用法

    SHOW PROFILE [type [, type] …… ] [FOR QUERY n] [LIMIT row_count [OFFSET offset]]
    
    type: { ALL | BLOCK IO | CONTEXT SWITCHES | CPU | IPC | MEMORY | PAGE FAULTS | SOURCE | SWAPS }

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 【Spring】BeanDefinition&PostProcessor不了解一下嗎?

    水稻:這两天看了BeanDefinition和BeanFactoryPostProcessor還有BeanPostProcessor的源碼。要不要了解一下

    菜瓜:six six six,大佬請講

    水稻:上次我們說SpringIOC容器是一個典型的工廠模式

    • 假如我們把Spring比作一個生產模型的大工廠,那麼.class文件就是原材料。而BeanDefinition就是創建模型的模具。不管是傳統的XML還是後面的註解,Spring在啟動的時候都會創建一個掃描器去掃描指定目錄下的.class文件,並根據文件的註解,實現的接口以及成員變量將其封裝一個個的BeanDefinition。
      • 比較重要的屬性有id,class,構造函數封裝類,屬性封裝類,factoryMethod等
    • 在對象初始化之前Spring會完成BeanDefinition對象的解析並將其裝入List容器beanDefinitionNames中,然後開始遍歷該容器並根據BeanDefinition創建對象

    菜瓜:sodasinei,BeanDefinition我了解了。它是創建bean的模板,類似於java創建對象依賴的class一樣。那還有兩個很長的單詞是啥呢?

    水稻:忽略掉後面老長的後綴,我們看BeanFactory和Bean是不是很親切。PostProcessor被翻譯成後置處理器,暫且我們把它看成是處理器就行

    • BeanFactory是bean工廠,它可以獲取並修改BeanDefinition的屬性,進而影響後面創建的對象。
    • Bean就是Spring的對象,這些個處理器才是真正處理bean對象的各個環節的工序,包括屬性,註解,方法

    菜瓜:有了模糊的概念,不明覺厲

    水稻:來,看demo

    package com.vip.qc.postprocessor;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * 獲取初始化好的BeanFactory,此時還未進行bean的實例化
     *
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class BeanFactoryPostProcessorT implements BeanFactoryPostProcessor {
    
        public static final String BEAN_NAME = "processorT";
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            BeanDefinition initializingBeanT = beanFactory.getBeanDefinition(BEAN_NAME);
            MutablePropertyValues propertyValues = initializingBeanT.getPropertyValues();
            String pName = "a";
            System.out.println("BeanFactoryPostProcessor a " + propertyValues.getPropertyValue(pName) + " -> 1");
            propertyValues.addPropertyValue(pName, "1");
        }
    }
    
    
    package com.vip.qc.postprocessor;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    /**
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class BeanPostProcessorT implements BeanPostProcessor {
    
        public static final String beanNameT = "processorT";
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (beanNameT.equals(beanName)) {
                ProcessorT processorT = ((ProcessorT) bean);
                System.out.println("BeanPostProcessor BeforeInitialization  a:" + processorT.getA() + "-> 3");
                processorT.setA("3");
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (beanNameT.equals(beanName)){
                ProcessorT processorT = ((ProcessorT) bean);
                System.out.println("BeanPostProcessor AfterInitialization  a:" + processorT.getA() + "-> 4");
                processorT.setA("4");
            }
            return bean;
        }
    
    }
    
    
    package com.vip.qc.postprocessor;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class ProcessorT {
    
        public ProcessorT() {
            System.out.println("ProcessorT 無參構造 a:" + a + "-> 2" );
            a = "2";
        }
    
        private String a;
    
        public String getA() {
            return a;
        }
    
        public void setA(String a) {
            this.a = a;
        }
    
        @Override
        public String toString() {
            return "ProcessorT{" +
                    "a='" + a + '\'' +
                    '}';
        }
    }
    
    // 測試類
    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.vip.qc.postprocessor");
        ProcessorT processorT = (ProcessorT) context.getBean("processorT");
        System.out.println(processorT);
    }
    
    // 結果
    BeanFactoryPostProcessor a null -> 1
    ProcessorT 無參構造 a:null-> 2
    BeanPostProcessor BeforeInitialization a:1-> 3
    BeanPostProcessor AfterInitialization a:3-> 4
    ProcessorT{a='4'}
    • BeanFactoryPostProcessor在對象還未初始化前可以拿到對象的BeanDefinition對其設置屬性值
    • 過程中我們分別對屬性a設置了1,2,3,4的值。最後我們拿到的值為4

    菜瓜:好像看懂了。BeanFactoryPostProcessor可以拿到BeanFactory對象,獲取裏面所有的BeanDefinition並可對其進行干預。BeanPostProcessor其實是在bean已經被創建完成之後進行加工操作

    水稻:沒錯。這是我們自己進行干預的demo。限於篇幅有限,你可以去看一下Spring自己對於這兩個接口的實現源碼。比較重要的推薦下面幾個

    • ConfigurationClassPostProcessor 實現BeanFactoryPostProcessor子接口
      • 完成對@Configuration、@Component、@ComponentScan、@Bean、@Import、@ImportSource註解的搜集和解析
      • @Bean註解會被封裝成所在Bean的BeanDefinition中的factoryMethod屬性中,單獨進行實例化
    • CommonAnnotationBeanPostProcessor 實現 BeanPostProcessor
      • 完成@PostConstruct@PreDestroy@Resource註解的搜集和解析工作
      • @PostConstruct會在對象初始化且屬性渲染完成後進行
      • @Resource註解(參照下面)
    • AutowiredAnnotationBeanPostProcessor 實現 BeanPostProcessor
      • 完成@Autowired@Value註解的搜集和解析工作
      • 在對象初始化完成之後會先進行註解的搜集,然後進行屬性渲染調用populateBean方法,使用策略模式調用實現接口對註解進行解析,有@Autowired和@Value註解會調用getBean方法發起對依賴屬性的注入
    • AbstractAutoProxyCreator的入口類也是實現的BeanPostProcessor

    菜瓜:你放心,我不會看的。這麼複雜的東西,聽着都費勁

    水稻:不愧是你!有機會聊bean的生命周期的時候咱們還會說到這些東西。到時候再刷一遍

     

    總結:

    • BeanDefinition是spring容器創建對象的模板,定義了bean創建的細節
    • BeanFactoryPostProcessor可以拿到整個容器對象,當然也能修改BeanDefinition,所以能直接操作bean的創建
    • BeanPostProcessor執行的時候bean已經創建完成了,我們可以拿到想要的對象進行干預和設值等操作

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

  • 您的單例模式,真的單例嗎?

    您的單例模式,真的單例嗎?

          單例模式,大家恐怕再熟悉不過了,其作用與實現方式有多種,這裏就不啰嗦了。但是,咱們在使用這些方式實現單例模式時,程序中就真的會只有一個實例嗎?

          聰明的你看到這樣的問話,一定猜到了答案是NO。這裏筆者就不賣關子了,開門見山吧!實際上,在有些場景下,如果程序處理不當,會無情地破壞掉單例模式,導致程序中出現多個實例對象。

          下面筆者介紹筆者已知的三種破壞單例模式的方式以及避免方法。

    1、反射對單例模式的破壞

          我們先通過一個例子,來直觀感受一下

        (1)案例

      DCL實現的單例模式:

     1 public class Singleton{
     2     private static volatile Singleton mInstance;
     3     private Singleton(){}
     4     public static Singleton getInstance(){
     5         if(mInstance == null){
     6             synchronized (Singleton.class) {
     7                 if(mInstance == null){
     8                     mInstance = new Singleton();
     9                 }
    10             }
    11         }
    12         return mInstance;
    13     }
    14 }

      測試代碼:

     1 public class SingletonDemo {
     2 
     3     public static void main(String[] args){
     4         Singleton singleton = Singleton.getInstance();
     5         try {
     6             Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
     7             constructor.setAccessible(true);
     8             Singleton reflectSingleton = constructor.newInstance();
     9             System.out.println(reflectSingleton == singleton);
    10         } catch (Exception e) {
    11             // TODO Auto-generated catch block
    12             e.printStackTrace();
    13         }
    14     }
    15 }

      執行結果:

    false

          運行結果說明,採用反射的方式另闢蹊徑實例了該類,導致程序中會存在不止一個實例。

        (2)解決方案

          其思想就是採用一個全局變量,來標記是否已經實例化過了,如果已經實例化過了,第二次實例化的時候,拋出異常。實現代碼如下:

     1 public class Singleton{
     2     private static volatile Singleton mInstance;
     3     private static volatile boolean mIsInstantiated = false;
     4     private Singleton(){
     5         if (mIsInstantiated){
     6             throw new RuntimeException("Has been instantiated, can not do it again!");
     7         }
     8         mIsInstantiated = true;
     9     }
    10     public static Singleton getInstance(){
    11         if(mInstance == null){
    12             synchronized (Singleton.class) {
    13                 if(mInstance == null){
    14                     mInstance = new Singleton();
    15                 }
    16             }
    17         }
    18         return mInstance;
    19     }
    20 }

    執行結果:

     

         這種方式看起來比較暴力,運行時直接拋出異常。

     

    2、clone()對單例模式的破壞 

           當需要實現單例的類允許clone()時,如果處理不當,也會導致程序中出現不止一個實例。

        (1)案例

      一個實現了Cloneable接口單例類:

     1 public class Singleton implements Cloneable{
     2     private static volatile Singleton mInstance;
     3     private Singleton(){
     4     }
     5     public static Singleton getInstance(){
     6         if(mInstance == null){
     7             synchronized (Singleton.class) {
     8                 if(mInstance == null){
     9                     mInstance = new Singleton();
    10                 }
    11             }
    12         }
    13         return mInstance;
    14     }
    15     @Override
    16     protected Object clone() throws CloneNotSupportedException {
    17         // TODO Auto-generated method stub
    18         return super.clone();
    19     }
    20 }

      測試代碼:

     1 public class SingletonDemo {
     2 
     3     public static void main(String[] args){
     4         try {
     5             Singleton singleton = Singleton.getInstance();
     6             Singleton cloneSingleton;
     7             cloneSingleton = (Singleton) Singleton.getInstance().clone();
     8             System.out.println(cloneSingleton == singleton);
     9         } catch (CloneNotSupportedException e) {
    10             e.printStackTrace();
    11         }
    12     }
    13 }

    執行結果:

    false

      (2)解決方案:

         解決思想是,重寫clone()方法,調clone()時直接返回已經實例的對象

     1 public class Singleton implements Cloneable{
     2     private static volatile Singleton mInstance;
     3     private Singleton(){
     4     }
     5     public static Singleton getInstance(){
     6         if(mInstance == null){
     7             synchronized (Singleton.class) {
     8                 if(mInstance == null){
     9                     mInstance = new Singleton();
    10                 }
    11             }
    12         }
    13         return mInstance;
    14     }
    15     @Override
    16     protected Object clone() throws CloneNotSupportedException {
    17         return mInstance;
    18     }
    19 }

    執行結果:

    true

     

    3、序列化對單例模式的破壞

       在使用序列化/反序列化時,也會出現產生新實例對象的情況。

      (1)案例

          一個實現了序列化接口的單例類:

     1 public class Singleton implements Serializable{
     2     private static volatile Singleton mInstance;
     3     private Singleton(){
     4     }
     5     public static Singleton getInstance(){
     6         if(mInstance == null){
     7             synchronized (Singleton.class) {
     8                 if(mInstance == null){
     9                     mInstance = new Singleton();
    10                 }
    11             }
    12         }
    13         return mInstance;
    14     }
    15 }

        測試代碼:

     1 public class SingletonDemo {
     2 
     3     public static void main(String[] args){
     4         try {
     5             Singleton singleton = Singleton.getInstance();
     6             FileOutputStream fos = new FileOutputStream("singleton.txt");
     7             ObjectOutputStream oos = new ObjectOutputStream(fos);
     8             oos.writeObject(singleton);
     9             oos.close();
    10             fos.close();
    11 
    12             FileInputStream fis = new FileInputStream("singleton.txt");
    13             ObjectInputStream ois = new ObjectInputStream(fis);
    14             Singleton serializedSingleton = (Singleton) ois.readObject();
    15             fis.close();
    16             ois.close();
    17             System.out.println(serializedSingleton==singleton);
    18         } catch (Exception e) {
    19             e.printStackTrace();
    20         }
    21 
    22     }
    23 }

         運行結果:

    false

        (2)解決方案

        在反序列化時的回調方法 readResolve()中返回單例對象。

     1 public class Singleton implements Serializable{
     2     private static volatile Singleton mInstance;
     3     private Singleton(){
     4     }
     5     public static Singleton getInstance(){
     6         if(mInstance == null){
     7             synchronized (Singleton.class) {
     8                 if(mInstance == null){
     9                     mInstance = new Singleton();
    10                 }
    11             }
    12         }
    13         return mInstance;
    14     }
    15 
    16     protected Object readResolve() throws ObjectStreamException{
    17         return mInstance;
    18     }
    19 }

    結果:

    true

     

           以上就是筆者目前已知的三種可以破壞單例模式的場景以及對應的解決辦法,讀者如果知道還有其他的場景,記得一定要分享出來噢,正所謂“獨樂樂不如眾樂樂”!!!

           單例模式看起來是設計模式中最簡單的一個,但“麻雀雖小,五臟俱全”,其中有很多細節都是值得深究的。即便是本篇介紹的這幾個場景,也只是介紹了一些梗概而已,很多細節還需要讀者自己去試驗和推敲的,比如:通過枚舉方式實現單例模式,就不存在上述問題,而其它的實現方式似乎都存在上述問題!

     

           後記

           本篇參(剽)考(竊)了如下資料:

           高洪岩的《Java 多線程編程核心技術》

           博文:https://blog.csdn.net/fd2025/article/details/79711198

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 都是30萬德系轎車,頂配邁騰和奧迪A4L到底差多少?

    都是30萬德系轎車,頂配邁騰和奧迪A4L到底差多少?

    其實邁騰多出來的配置不止這些,還有二十多項,全部寫出來太長,故略去。低配的奧迪A4L採用了低功率的2。0T發動機,最大馬力是190匹,最大扭矩是320牛•米。頂配的邁騰使用的2。0T發動機賬面數據更好,220匹的最大馬力和350牛•米的最大扭矩均優於A4L。

    隨着平台化,規模化生產的普及,很多大集團都會在自己的普通品牌和豪華品牌上通用零件,但是定價卻差了幾萬,那麼這幾萬的差價真的如大家所說,是LOGO的差異嗎?同一個集團,應該買TA的普通品牌高配車型還是豪華品牌的低配車型比較划算?

    今天,我們來對比一下大眾邁騰的380TSI 旗艦版和奧迪A4L的40TFSI 進取版,看看哪個更值得買。

    邁騰和奧迪A4L的外觀是同一個風格的兩份答卷。邁騰的橫貫式前臉把大燈連接起來,從視覺上來看顯得更寬更低矮。今天對比的邁騰380TSI 旗艦版配備的是18寸輪轂,所以邁騰的側面看上去更加大氣一些。

    奧迪A4L則是在原有的六邊形格柵的家族設計元素上再下功夫,把大燈、格柵、車身線條設計得再銳利一些,所以奧迪A4L的外觀顯得更加年輕。不過這17寸的輪轂嘛,說實話不是很能襯托出A4L的檔次,建議後期更換。

    這一局,邁騰略勝。

    這一代的邁騰是歐版帕薩特直接拿來生產的,所以這一代邁騰的風格反而是繼承了上一代帕薩特的商務風。在邁騰上,木紋飾板貫穿整个中控台,空調出風口被設計得更高,所以空間更加寬闊。奧迪A4L的內飾以科技感為主要設計導向,奧迪A4L 30TSI 進取版採用中控台採用了銀色拉絲面板,中控屏採用懸浮式設計。可惜的是低配車沒有使用全液晶显示屏,空調面板也不是高配的那款金屬質感控制面板。所以質感稍顯不足。

    這一局,也是邁騰勝出。

    無論是邁騰還是奧迪A4L,引進國內后,都進行了不同程度的加長,邁騰的軸距為2871mm,比海外版增加了80mm,奧迪A4L的軸距是2908mm,比海外版增加了88mm。雖然奧迪A4L的軸距比邁騰要長一點,但是乘坐空間方面,邁騰比奧迪A4L要寬敞一點。這是因為邁騰是MQB(發動機橫置平台)的產物,而奧迪A4L是MLB(發動機縱置平台)的產物。發動機橫置的結構比較緊湊,占乘坐空間較少,所以邁騰的乘坐空間比奧迪A4L稍微大一點。

    這一局,因為兩者的差距微乎其微,所以判定兩者打成平手。

    在配置上,邁騰完勝沒什麼懸念。其實邁騰多出來的配置不止這些,還有二十多項,全部寫出來太長,故略去。

    低配的奧迪A4L採用了低功率的2.0T發動機,最大馬力是190匹,最大扭矩是320牛•米。頂配的邁騰使用的2.0T發動機賬面數據更好,220匹的最大馬力和350牛•米的最大扭矩均優於A4L。從加速成績來看,邁騰比奧迪A4L更好,但也只是半秒的差距。奧迪A4L的發動機有AVS可變氣門升程技術和混合噴射技術。這兩項技術都可以提高燃油經濟性,所以從用戶反饋的數據來看,奧迪A4L比邁騰節油不少。

    所以這局,也只能判五五開。

    說到舒適性方面奧迪A4L的前後懸挂都使用了五連桿獨立懸挂,而邁騰則採用了前麥弗遜獨立懸挂后多連桿獨立懸挂的組合。這不僅是因為成本,還因為邁騰使用的MQB佔據了機艙空間,所以無法布局多連桿懸挂。麥弗遜式獨立懸挂有個缺點,那就是上下運動的時候對車輪外傾角變化明顯,而車輪外傾角變化會影響操縱的穩定性。所以,為了安全性考慮,麥弗遜懸挂一般都會比較硬。而使用五連桿的奧迪A4L可以把懸挂調得軟一些,所以奧迪A4L的濾震更好。在加上在隔音材料方面,豪華品牌比普通品牌的投入會更大。

    所以舒適性方面,奧迪A4L完勝邁騰是沒什麼懸念的。

    總結:縱觀兩車各個方面的對比,互有勝負。豪華品牌的車不是只掛着一個LOGO就賣這麼貴,如果你坐過邁騰后再坐A4L,你確確實實能夠感受到豪華車帶給你的那種舒適感,高級感。但是在靜態體驗的時候,邁騰的頂配版可以說是完虐奧迪A4L了。所以,我建議,如果你打算購買一台商務車,邁騰的頂配版更加合適。如果只是買來自用,奧迪A4L值得考慮。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※教你寫出一流的銷售文案?

  • 要成為合格的女司機,最大的挑戰是什麼?

    要成為合格的女司機,最大的挑戰是什麼?

    可是,要完成這一次挑戰,我必須要有一台車。這次和我一起挑戰的,是全新昂科威28T頂配車型,2。0T發動機搭配9AT變速箱,擁有260匹馬力,還配備了CDC主動懸挂。2。0T發動機符合我對動力跟經濟性的需求。相比以前,全新的9AT變速箱也足夠聰明,足以應付多種路況。

    大家好,我是小喬,說真的,我有點害怕。

    作為一個女司機,我覺得我的駕駛技術算挺好了,但為什麼,我的知名程度,遠遠不如那個長得跟我差不多的Jacky呢?

    到底是“7200干它”成就了他,還是他成就了“7200干它”,這個問題,在我腦海中迴旋,如同先有雞還是先有蛋的生物史拷問,令我疑惑不已。終於,我決定卧薪嘗膽,直接跟他當面對質爆紅的真諦!

    Jacky面對我的質疑,嘴角微微上揚,說到:“既然你誠心誠意想要爆紅,那我就大發慈悲地成全你, 別克SUV強者挑戰之旅,這個對精神以及體力都有極大考驗的活動,就決定讓你參加了!“

    挑戰內容

    挑戰一:體能挑戰:限時挑戰3公里高空棧道。優秀的司機必須擁有強壯的體能!

    挑戰二:耐力測試,獨自跑完4個小時高速全程,鍛煉作為司機的集中力和耐久力。

    挑戰三:膽量挑戰:攀登懸崖天梯,鍛煉司機膽大心細的危急情況處理能力。

    挑戰四:險中求勝:考驗司機越野路面的駕駛能力。

    手拿這份挑戰清單,我感到一絲恐懼,但誓要成為女車神的我,不會輕易認輸!

    可是,要完成這一次挑戰,我必須要有一台車。這次和我一起挑戰的,是全新昂科威28T頂配車型,2.0T發動機搭配9AT變速箱,擁有260匹馬力,還配備了CDC主動懸挂。

    2.0T發動機符合我對動力跟經濟性的需求;相比以前,全新的9AT變速箱也足夠聰明,足以應付多種路況;它的懸架還能主動變化,公路、越野我都能舒舒服服。這一次挑戰,我勢在必得。

    想知道到底Jacky給小喬的挑戰有多可怕?讓小喬這個沒心沒肺的女人都感到驚慌,而她又能否順利完成任務?趕緊點開視頻看一看!

    本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 大尺寸+高配置 眾泰T800 13.98萬起售

    大尺寸+高配置 眾泰T800 13.98萬起售

    4英寸中控屏第一時間抓住你的眼球,全車的功能基本都集成在裏面,旋鈕式換擋,全液晶儀錶,大面積軟性材料的包裹,檔次感還是營造的不錯,雖然說中控UI界面布局和反應速度還有提升的空間,但結合其價格來看,也能讓你產生“要啥自行車”的感慨了。

    眾泰T800在5月8日晚正式上市,新車共推出五款車型,售價13.98-18.58萬元。T800作為眾泰首款七座中型SUV,其配置同尺寸都達到了品牌的新高度。動力總成為2.0T+6DCT,從某種角度上T800可視作T700的加長版本。

    眾泰T800在外觀設計上可以說擁有相當高的原創度,前臉進氣格柵的面積非常大,採用星空點陣的設計元素,下部格柵採用了中國風的回紋設計,凸顯了整個正臉的設計感。頭燈部分採用了全LED光源,轉向燈則單獨設計在下部,同樣使用了LED,只有最下面的霧燈為鹵素光源,配置上十分厚道。

    T800的整車尺寸為4910x1933x1735mm,軸距2850mm,在目前自主中型七座SUV中算比較大的,側門上用裝飾模擬了出風口的造型,並且還點綴有眾泰漢語拼音首字母“Z”字形的標誌,同時不忘加入一個裝飾燈,這樣的小心思在其它品牌上不多見。還了解到,T800的頂配車型搭載了電吸門,在這個級別中是獨一份的那個。

    進入車內,10.4英寸中控屏第一時間抓住你的眼球,全車的功能基本都集成在裏面,旋鈕式換擋,全液晶儀錶,大面積軟性材料的包裹,檔次感還是營造的不錯,雖然說中控UI界面布局和反應速度還有提升的空間,但結合其價格來看,也能讓你產生“要啥自行車”的感慨了。

    談到眾泰,怎能不提配置,T800的配置水平用“極限”形容毫不過分,全景天窗,360°影像這些只是基本操作,什麼電動腳踏,電吸門,氛圍燈,車聯網,前窗加熱······配置上堆的滿滿噹噹,重要的是,除了入門車型以外,很多配置都實現了全系覆蓋,可以說,T800隻要不買最低配,都可以達到越級體驗。

    T800的2.0T動力,參數上達到了170kW/350N·m,在自主眾多中型七座中屬於動力靠前的那個,匹配六速雙離合變速箱,或許表現上沒有眾多愛信6AT那麼出色,但想想車內那麼豐富的配置,也能理解。前麥弗遜+后多連桿懸架是這個級別的標配,沒有四驅車型有些遺憾。但考慮其“城市SUV”的身份,也不會是什麼大問題。

    對於T800,我們先拋開眾泰眾多的“歷史遺留問題”去看待它。設計有原創,配置高上天,價格有驚喜——這三點已經是很大的亮點。自主品牌中型七座SUV已經很多,但T800還是能找到獨特的競爭力。能不能賣爆款不好說,但還是能吸引部分消費者的眼球,至少我們能看到的,T800已經是眾泰拿出最大的誠意去做的產品。品牌和質量都是需要長時間積累的事情,眾泰想要像其他自主品牌一樣深入民心,還是有很長的路要走。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 7.99萬起,今天上市的大空間6座車真是家用好選擇!

    7.99萬起,今天上市的大空間6座車真是家用好選擇!

    尾部在擾流板、貫穿尾燈的鍍鉻裝飾、黑色塑料擋板的組合下,形成了一種層次感。敦厚的車尾部,更是讓它充滿安全感。仿木材料的中控台,還有一個大尺寸豎直中控屏幕,以及是手感極佳的平底式運動方向盤,它在簡約風格中加入一點運動的元素。

    “一輛車究竟是多少座位最好?”,除了自己,還有配偶,加上父母以及自己的孩子,或許還是雙胞胎,這樣看來起碼是6座車型,另外一般而言緊湊型轎車後排乘坐3成年人是比較難受的事情。

    正是為了滿足用戶這種需求,比亞迪宋MAX前期推出了7座車型,如今更是推出了6座車型,官方指導價為:7.99-12.99萬。那麼它表現如何呢?

    6座,就是它最大的不同

    6座版本的比亞迪宋max依然有着4680*1810*1680mm的車身尺寸,以及是能夠帶來充足空間表現的2785mm軸距。並且在座椅配置上一樣保留了7座車型的優點,諸如第三排寬大保護作用大的頭枕、座椅採用打孔真皮材質。

    它採用了2+2+2的6座布局,獨立第二排座椅可以帶來比7座車型更舒適的體驗,加上獨立的可調節座椅扶手,座椅包裹性更佳。中部通道寬度為190mm,乘客進出第三排更為優雅。

    在實際的乘坐它表現讓人滿意,前排以及第二排座椅包裹性極佳,並且頂配車型上前排座椅還具備主副駕座椅通風加熱、第二排預警限力安全帶等配置。唯獨是第三排由於車身結構問題坐墊與地板距離較近,腿部支撐較弱,中長途旅行中更適合乘坐小孩子或者是身材嬌小的成年人。

    設計以及配置,和7座車型無異

    6座版本的宋MAX在設計上和7座車型保持了一致,犀利大燈帶有的鍍鉻裝飾條以及LED日間行車燈和大尺寸進氣格柵橫向裝飾條連成一體,拉寬了視覺寬度。這個“大嘴”配上這兇狠犀利的大燈,就形成了它’Dragon Face”設計的基本元素。

    4680mm車身長度的它在逐漸下滑的懸浮式車頂襯托下,顯得車身相當修長,低矮的腰線則降低了它的視覺重心,讓它整體有着一種動感、衝勁。

    尾部在擾流板、貫穿尾燈的鍍鉻裝飾、黑色塑料擋板的組合下,形成了一種層次感。敦厚的車尾部,更是讓它充滿安全感。

    仿木材料的中控台,還有一個大尺寸豎直中控屏幕,以及是手感極佳的平底式運動方向盤,它在簡約風格中加入一點運動的元素。

    生產實力強大、自產自銷的比亞迪能夠有效控製成本,所以它在配置配備上相當慷慨,標配了ESp車身穩定系統、17英寸鋁合金輪轂、自動空調、遠程啟動等等。又或者是一些“跨級般”的配置,諸如選配的LED自動大燈。

    操控,或許可以說沉穩自信?

    比亞迪宋MAX全系車型採用前麥弗遜后扭力梁懸架,處於這個級別的主流水平。動力方面搭載了最大輸出功率154馬力的1.5T直噴發動機,匹配的是6MT或者6速雙離合變速箱。

    在日常使用中它整體表現出同價位中較高的水準,應付城市使用這套渦輪增壓+雙離合的動力總成顯得游刃有餘,能夠有效地控制轉速在1500rpm之中,需要提速的時候154馬力的底蘊讓超車動作比較從容,而且它1750rpm就開始進入240牛米的最大輸出扭矩區間,相比競爭對手們最大扭矩輸出更早。只是低速在1、2擋切換時會有絕大多數雙離合變速箱的小不足——輕微頓挫感。

    據比亞迪官方稱,比亞迪宋max有着前奔馳底盤調校工程師漢斯柯克參与調校,讓它更注重舒適性,所以宋max駕駛起來有着厚重的行駛質感。能夠過濾掉路面各種細碎振動的同時,還有讓人安心的高速穩定性。受制於全系搭配的液壓轉向助力,宋max轉向略有虛位,並不可以說是精準,幸好較強的懸架支撐帶來了較小的車身傾側,扳回了一城。

    總的來說,比亞迪宋max相比同價位對手有着不少的優勢,諸如更佳的底盤表現、更豐富的配置以及更出色的外觀,這恰恰迎合如今消費者開始接受這些“體面”MpV的用戶趨勢。6座車型,則是滿足了消費者不同的乘坐需求,多一種選擇未嘗不是一種好事。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 6秒破百很厲害?國內10來萬買SUV更應該看這點!

    6秒破百很厲害?國內10來萬買SUV更應該看這點!

    值得說明的是,國內媒體測試的多為旱地剎車成績,而在售車型大多採用四季胎,在暴雨天氣下剎車距離或會明顯變長,所以各位在暴雨天氣下最好加長與前車距離。我們可以看到市面上多款SUV車型的100-0km/h剎停成績差異頗大,這也是因為大部分用戶對於剎車方面的關注度不高,導致廠家有出現“偷懶”情況。

    大家有沒有發現汽車廠家往往會把宣傳重點放在動力性能和發動機技術上,但是對於剎車性能往往置於末位。而只要您多加留意,就會和朗編一樣發現各個級別中不少熱點車型都以動力性能作為賣點!

    連柯迪亞克、途觀L和冠道這類型大尺寸的SUV車型都在極力宣傳動力性能,但這明顯是為迎合國內消費者喜好而做出的選擇,畢竟用戶大多認為動力表現能直接決定行駛的順暢感,或者說是快感!但是這份快感的權重能比得上行車安全性嗎?

    再者,我國的高速公路最高限速為120km/h,過多的發動機功率是否必要還有待商榷。

    而在我們日常行車過程中,能看見最多的車禍是啥?可能許多人會和朗編一樣會想到追尾事故,四車連環追尾、六車連環追尾等事故經常霸佔着新聞的首頁版面,而且在雨霧天氣,追尾事故發生的幾率也將大幅度上升。

    而據之前相關的統計數據显示,汽車追尾在整個道路交通事故中約佔70%以上,造成追尾事故自然有一部分是駕駛員意識、駕駛技術的原因,同時也有一部分是駕駛員意識到位了但車輛的剎車性能不到位而造成的。

    現在銷售火爆的15萬級別合資/自主SUV車型它們是否注重剎車性能?

    值得說明的是,國內媒體測試的多為旱地剎車成績,而在售車型大多採用四季胎,在暴雨天氣下剎車距離或會明顯變長,所以各位在暴雨天氣下最好加長與前車距離!

    我們可以看到市面上多款SUV車型的100-0km/h剎停成績差異頗大,這也是因為大部分用戶對於剎車方面的關注度不高,導致廠家有出現“偷懶”情況。

    其實能影響剎車性能的元素有很多,除了輪胎規格、輪胎抓地力以外,還有剎車片和剎車盤的性能,剎車片和剎車盤的材料優劣,摩擦係數的大小都會直接影響到制動力的大小!

    而且值得注意的還有剎車分泵的活塞數量,一般家用車型每個分泵只有一個活塞,其性能自然沒有性能車身上的多活塞設計那般剎車力度大而且均勻。

    由此可見想要提升剎車性能其實是相當複雜而困難的,比較有效的方法是系統而專業地改裝剎車系統,這裏面包含了輪胎、剎車盤、剎車片、剎車分泵、剎車油等多個配件的升級。

    另外一個方法就是廠家在研發車輛時強化車輛剎車系統的調校,優化配件選材以提升原廠車的剎車表現,當然了這要投入更多成本,但是長遠來看,廠家若以優異的剎車性能來作為賣點招攬顧客的話,或會效果斐然!

    最後提點題外話,那就是歐洲人為什麼會這麼喜歡高爾夫GTI這一類小鋼炮車型?很重要的就是歐洲的某些高速公路限速更高,在高速區域剎車性能的重要性就完全體現出來了,對於安全性也有更明顯的影響!而咱們國內的汽車消費市場也應該對剎車性能有更高要求,從而倒逼廠家重視提升剎車性能!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

  • 【溫故知新】 編程原則和方法論

    寫了這麼多年代碼,依舊做不好一個項目

    做好一個項目是人力、產品、業務、技術、運營的結合,可能還疊加一點時機的因素,就我們碼農而言,工作就是搬磚,實現產品, 給業務提供支撐。
    “給祖傳代碼加 BUG 修 BUG”,“拿起鍵盤一把梭”這些戲謔程序員的話,聽多了真的會讓程序員麻木,彷彿大家都是這麼乾的。
    從業多年,堆過 shi 山,接手過祖傳代碼, 已經不能沉下氣去查看、調試 shi 山代碼, 說實話,很累。
    本人一直推崇寫流暢、自然、可自解釋的代碼,讓優雅成為一種習慣, 給自己留個念想、給後人留個好評。

    溫故而知新,聊一聊現代編程幾大常見的編程原則

    普世原則
    KISS (Keep It Simple Stupid) 保持系統結構簡單可信賴
    YAGNI (you aren’t gonna need it) 當前確實需要,再去做
    Do The Simplest Things That Could Possibly Work 思考最簡單可行的辦法
    Separation of Concerns 關注點分離
    Keep Things DRY 保持代碼結構清爽 Don’t repeat yourself
    Code For The Maintainer 站在維護者角度寫代碼
    Avoid Premature Optimization 避免提前優化
    Boy-Scout Rule 清掃戰場:清理口水話註釋、無效代碼
    模塊(類)間
    Minimise Coupling 低耦合
    Law of Demeter Don’t talk to strangers,對象方法只接觸該接觸的對象、字段、入參
    Composition Over Inheritance 組合而不是繼承
    Orthogonality 正相關,概念上不相關的事物不應在系統中強行相關
    Robustness Principle 代碼健壯性
    Inversion of Control 控制反轉
    模塊(類)
    Maximise Cohesion 高內聚
    Likov Substitution Principle 里斯替代原則:將程序中對象替換到子類型實例,不會報錯。
    Open/Closed Principle 設計的實體對擴展開放,對修改關閉
    Single Responsiblity Principle 單一責任原則
    Hide Implementation Details 隱藏實施細節
    Curly’s Law 柯里定律:為確定目標編寫特定代碼
    Encapsulate What Changes 封裝變化
    Interface Segregation Principle 接口隔離原則
    Command Query Separation 命令查詢分離

    KISS
    大多數系統保持簡單,會運行的很好。

    • 更少的代碼消耗更好的時間,產生更少的 bug,並且容易修改
    • 複雜業務都是由簡單代碼堆砌而成
    • 完美並不是“沒有什麼東西可以再加”,而是“沒有什麼東西可以被去掉”

    YAGNI
    YAGNI 代表“you aren’t gonna need it.”,不要自以為是的提前實現某些邊角,直到真正需要的時候,再來做。

    • 提前做明天才需要做的工作,意味着當前迭代中需要花費更多精力
    • 導致代碼膨脹,軟件變得臃腫且複雜

    Separation of Concerns
    關注點分離是一種將計算機程序分為不同部分的設計原則,這樣每個部分都可以解決一個單獨的關注點。例如應用程序的業務邏輯是一個問題,而用戶界面是另外一個問題,更改用戶界面不應要求更改業務邏輯,反之亦然。

    • 簡化應用程序的開發和維護
    • 如果關注點分離得很好,則各個部分可以重複使用,也可以獨立開發和更新。

    Interface Segregation Principle
    接口隔離,將胖接口修改為多個小接口,調用接口的代碼應該比實現接口的代碼更依賴於接口。
    why:
    如果一個類實現了胖接口的所有方法(部分方法在某次調用時並不需要),那麼在該次調用時我們就會發現此時出現了(部分並不需要的方法),而並沒有機制告訴我們我們現在不應該使用這部分方法。
    how: 避免胖接口,類永遠不必實現違反單一職責原則的接口。可以根據實際多職責劃分為多接口,類實現多接口后, 在調用時以特定接口指代對象,這樣這個對象只能體現特定接口的方法,以此體現接口隔離。

       public interface IA
        {
            void getA();
        }
    
        interface IB
        {
            void getB();
        }
    
        public class Test : IA, IB
        {
            public string Field { get; set; }
            public void getA()
            {
                throw new NotImplementedException();
            }
    
            public void getB()
            {
                throw new NotImplementedException();
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
    
                IA a = new Test();
                a.getA();       //  在這個調用處只能看到接口IA的方法, 接口隔離
            }
        }
    

    Command Query Separation
    命令查詢分離: 操作方法就只寫操作邏輯,查詢方法就只寫查詢邏輯,並以明顯的方法名區分自己的動作。
    有了這個原則,程序員可以更加自信地進行編碼:由於查詢方法不會改變狀態,因此可以在任何地方以任何順序使用,使用操作方法時,也心中有數。

    End

    懂得這麼多道理,卻依舊過不好這一生。前人總結的編程原則和方法論需要在實踐中感悟,束之高閣,則始終不能體會編程的魅力和快感

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • LeetCode 77,組合挑戰,你能想出不用遞歸的解法嗎?

    LeetCode 77,組合挑戰,你能想出不用遞歸的解法嗎?

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是LeetCode第46篇文章,我們一起來LeetCode中的77題,Combinations(組合)。

    這個題目可以說是很精闢了,僅僅用一個單詞的標題就說清楚了大半題意了。這題官方難度是Medium,它在LeetCode當中評價很高,1364人點贊,只有66個反對。通過率53.6%。

    題意

    題目的題意很簡單,給定兩個整數n和k。n表示從1到n的n個自然數,要求隨機從這n個數中抽取k個的所有組合

    樣例

    Input: n = 4, k = 2
    Output:
    [
      [2,4],
      [3,4],
      [2,3],
      [1,2],
      [1,3],
      [1,4],
    ]
    

    全排列的問題我們已經很熟悉了,那麼獲取組合的問題怎麼做呢?

    遞歸

    這是一個全組合問題,實際上我們之前做過全排列問題。我們來分析一下排列和組合的區別,可能很多人知道這兩者的區別,但是對於區別本身的理解和認識不是非常深刻。

    排列和組合有一個巨大的區別在於,排列會考慮物體擺放的順序。也就是說同樣的元素構成,只要這些元素一些交換順序,那麼就會被視為是不同的排列。然而對於組合來說,是不會考慮物體的擺放順序的。只要是這些元素構成,無論它們怎麼調換擺放順序,都是同一種組合。

    我們獲取全排列的時候用的是回溯法,我們當然也可以用回溯法來獲取組合。但問題是,我們怎麼保證獲取到的組合都是元素的組成不同,而不是元素之間的順序不同呢?

    為了保證這一點,需要用到一個慣用的小套路,就是通過下標遞增來控制拿取元素的順序。如果我們限定了拿取元素的下標是遞增的,那麼就可以保證每一次拿取到的組合都是獨一無二的。所以我們就把這一點加在回溯法上即可,只要理解了,並不難實現。

    在代碼的實現當中,我們用上了閉包,省略了幾個參數的傳遞,整體上來說編碼的難度降低了一些。

    class Solution:
        def combine(self, n: int, k: int) -> List[List[int]]:
            def dfs(start, cur):
                # 如果當前已經拿到了K個數的組合,直接加入答案
                # 注意要做深拷貝,否則在之後的回溯過程當中變動也會影響結果
                if len(cur) == k:
                    ret.append(cur[:])
                    return
                
                # 從start+1的位置開始遍歷
                for i in range(start+1, n):
                    cur.append(i+1)
                    dfs(i, cur)
                    # 回溯
                    cur.pop()
                    
            ret = []
            dfs(-1, [])
            return ret
    

    迭代

    這題並不是只有一種做法,我們也可以不用遞歸實現算法。不用遞歸意味着沒有系統幫助我們建棧存儲中間信息了,需要我們自己把迭代過程當中所有變量的關係整理清楚。

    我們假設n=8,k=3,那麼在所有合法的組合當中,最小的組合一定是[1,2,3],最大的組合一定是[6,7,8]。如果我們保證組合當中的元素是有序排列的,那麼組合之間的大小關係也是可以確定的。進而我們可以思考設計一種方案,使得我們可以從最小的組合[1,2,3]一直迭代到[6,7,8],並且我們還要保證在迭代的過程當中,組合當中元素的順序不會被打亂。

    我們可以想象成這n個數在一根“直尺”上排成了一行,我們有k個滑動框在上面移動。這k個滑動框取值的結果就是n個元素中選取k個的組合,並且由於滑動框之間是不能交錯的,所以保證了這k個值是有序的。我們要做的就是設計一種移動滑動框的算法,使得能夠找到所有的組合情況。

    我們可以想象一下,一開始的時候滑動框都聚集在最左邊,我們要移動只能移動最右側的滑動框。我們把滑動框從k移動到了k+1,那麼這個時候它的右側有k-1個滑動框,一共有k個位置。

    那麼這個問題其實轉化成了k個元素當中取k-1個組合的子問題。我們把1-k的這個部分看成是新的“直尺”,我們要在其中移動k-1個滑動框獲取所有的組合。首先,我們需要把這k-1個滑動框全部移動到左側,然後再移動其中最右側的滑動框。然後循環往複,直到所有的滑動框都往右移動了一格為止,這其實是一個遞歸的過程。

    我們不去深究這個遞歸的整個過程,我們只需要理解清楚其中的幾個關鍵點就可以了。首先,對於每一次遞歸來說,我們只會移動這個遞歸範圍內最右側的滑動框,其次我們清楚每一次遞歸過程中的起始狀態。開始狀態就是所有的滑動框全部集中在“直尺”的最左側,結束狀態就是全部集中在最右側。

    我們把上面的邏輯整理一下,假設我們經過一系列操作之後,m個滑動框全部移動到了長度為n的直尺的最右側。這就相當於的組合都已經獲取完了。如果n+1的位置還有滑動框,並且它的右側還可以移動,那麼我們需要將它往右移動一個,到n+2的位置。這個時候剩下的局面就是,為了獲取這些組合,我們需要把這m個滑動框全部再移動到直尺的最左側,重新開始移動。

    我們在實現的時候當然沒有滑動框,我們可以用一個數組記錄滑動框當中的元素。

    我先用遞歸寫一下這段邏輯:

    class Solution:
        def combine(self, n: int, k: int) -> List[List[int]]:
            def comb(window, m, ret):
                ret.append(window[:-1])
    
                # 如果第m位的滑動框不超過直尺的範圍並且m右側的滑動框
                while window[m] < min(n - k + m + 1, window[m+1] - 1):
                    # 向右滑動一位
                    window[m] += 1
                    # 如果m左側還有滑動框,遞歸
                    if m > 0:
                        # 把左側的滑動框全部移動到最左側
                        window[:m] = range(1, m+1)
                        comb(window, m-1, ret)
                    else:
                        # 否則記錄答案
                        ret.append(window[:-1])
    
                    
            ret = []
            window = list(range(1, k+1))
            # 額外多放一個滑動框作為標兵
            window.append(n+1)
            comb(window, k-1, ret)
            return ret
    

    這種解法的速度比上面正規遞歸的速度快了許多,因為我們遞歸的過程當中做了諸多限制,剪掉了很多無關的情況,相當於做了極致的剪枝。

    最關鍵的是上面的這段邏輯我們是可以用循環實現的,所以我們可以用循環來將遞歸的邏輯展開,就得到了下面這段代碼。

    class Solution:
        def combine(self, n: int, k: int) -> List[List[int]]:
            # 構造滑動框
            window = list(range(1, k + 1)) + [n + 1]
            
            ret, j = [], 0
    
            while j < k:
                # 添加答案
                ret.append(window[:k])
    
                j = 0
                # 從最左側的滑動框開始判斷
                # 如果滑動框與它右側滑動框挨着,那麼就將它移動到最左側
                # 因為它右側的滑動框一定會向右移動
                while j < k and window[j + 1] == window[j] + 1:
                    window[j] = j + 1
                    j += 1
                # 連續挨着最右側的滑動框向右移動一格
                window[j] += 1
                
            return ret
    

    這段代碼雖然非常精鍊,但是很難理解,尤其是你沒能理解上面遞歸實現的話,會更難理解。所以我建議,先把遞歸實現的滑動框的方法理解了,再來理解不含遞歸的這段,會容易一些。

    總結

    我們通過回溯法求解組合的方法應該是最簡單也是最基礎的,難度也不大。相比之下後面一種方法則要困難許多,我們直接去啃,往往不得要領。既會疑惑為什麼這樣可以保證能獲得所有的組合,又會不明白其中具體的實現邏輯。所以如果想要弄明白第二種方法,一定要從滑動框這個模型出發

    從代碼實現的角度來說,滑動框方法的遞歸解法比非遞歸的解法還要困難。因為遞歸條件以及邏輯都比較複雜,還涉及到存儲答案的問題。但是從理解上來說,遞歸的解法更加容易理解一些,非遞歸的算法往往會疑惑於j這個指針的取值。所以如果想要理解算法的話,可以從遞歸的代碼入手,想要實現代碼的話,可以從非遞歸的方法入手。

    這道題目非常有意思,值得大家細細思考。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!