<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SciAes</title>
  
  <subtitle>机器学习的自我重建之路</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://alexjiangzy.com/"/>
  <updated>2024-09-08T16:13:39.693Z</updated>
  <id>http://alexjiangzy.com/</id>
  
  <author>
    <name>Alex Jiang</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Deep learning</title>
    <link href="http://alexjiangzy.com/2024/07/23/DeepLearning/"/>
    <id>http://alexjiangzy.com/2024/07/23/DeepLearning/</id>
    <published>2024-07-23T15:49:17.000Z</published>
    <updated>2024-09-08T16:13:39.693Z</updated>
    
    <content type="html"><![CDATA[<h2 id="FM">FM</h2><pre><code class="language-python">class FactorizationMachine(tf.keras.Model):    def __init__(self, num_features, embedding_size):        super(FactorizationMachine, self).__init__()        self.num_features = num_features        self.embedding_size = embedding_size                # 初始化参数        self.w0 = tf.Variable(tf.zeros([1]), name='bias')        self.w = tf.Variable(tf.random.normal([num_features, 1]), name='linear_weights')        self.V = tf.Variable(tf.random.normal([num_features, embedding_size]), name='interaction_weights')    def call(self, inputs):        # embedding 部分        embedding_first = tf.nn.embedding_lookup(self.w, inputs)        embedding = tf.nn.embedding_lookup(self.V, inputs)        value = tf.reshape(inputs, [-1, self.num_features, 1])        embedding_value = tf.multiply(embedding, value)                # 前向传播        # first order 部分        first_order = tf.reduce_sum(tf.multiply(embedding_first, value), axis=1)        liner = tf.add(self.w0, first_order)                # second order 部分        mul_square = tf.square(tf.reduce_sum(embedding_value, 1))        square_mul = tf.reduce_sum(tf.square(embedding_value), 1)        second_order = 0.5 * tf.reduce_sum(tf.subtract(mul_square, square_mul), 1, keepdims=True)                # FM 模型的预测值        fm_model = tf.add(liner, second_order)        return tf.nn.sigmoid(fm_model)</code></pre><a id="more"></a><h2 id="MHA">MHA</h2><pre><code class="language-python">import tensorflow as tfdef scaled_dot_product_attention(Q, K, V, mask=None):    &quot;&quot;&quot;    计算缩放点积注意力    Q: Queries shape == (..., seq_len_q, depth)    K: Keys shape == (..., seq_len_k, depth)    V: Values shape == (..., seq_len_v, depth_v)    mask: Float tensor with shape broadcastable to (..., seq_len_q, seq_len_k)    &quot;&quot;&quot;    matmul_qk = tf.matmul(Q, K, transpose_b=True)  # (..., seq_len_q, seq_len_k)        # 缩放    dk = tf.cast(tf.shape(K)[-1], tf.float32)    scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)        # 添加掩码（mask）    if mask is not None:        scaled_attention_logits += (mask * -1e9)        # softmax归一化    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)        output = tf.matmul(attention_weights, V)  # (..., seq_len_q, depth_v)        return output, attention_weightsclass MultiHeadAttention(tf.layers.Layer):    def __init__(self, d_model, num_heads):        super(MultiHeadAttention, self).__init__()        self.num_heads = num_heads        self.d_model = d_model                assert d_model % self.num_heads == 0                self.depth = d_model // self.num_heads        def split_heads(self, x, batch_size):        &quot;&quot;&quot;        分割最后一个维度到 (num_heads, depth).        转置结果使得形状为 (batch_size, num_heads, seq_len, depth)        &quot;&quot;&quot;        x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))        return tf.transpose(x, perm=[0, 2, 1, 3])        def call(self, v, k, q, mask):        batch_size = tf.shape(q)[0]                q = tf.layers.dense(inputs=q, units=self.d_model, use_bias=False)  # (batch_size, seq_len, d_model)        k = tf.layers.dense(inputs=k, units=self.d_model, use_bias=False)  # (batch_size, seq_len, d_model)        v = tf.layers.dense(inputs=v, units=self.d_model, use_bias=False)  # (batch_size, seq_len, d_model)                q = self.split_heads(q, batch_size)  # (batch_size, num_heads, seq_len_q, depth)        k = self.split_heads(k, batch_size)  # (batch_size, num_heads, seq_len_k, depth)        v = self.split_heads(v, batch_size)  # (batch_size, num_heads, seq_len_v, depth)                scaled_attention, attention_weights = scaled_dot_product_attention(q, k, v, mask)        scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])  # (batch_size, seq_len_q, num_heads, depth)        concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model))  # (batch_size, seq_len_q, d_model)        output = tf.layers.dense(inputs=concat_attention, units=self.d_model, use_bias=False)  # (batch_size, seq_len_q, d_model)        return output, attention_weights</code></pre><h2 id="DIN-简单">DIN 简单</h2><pre><code class="language-python">def din(self, queries, keys, layer_size):    &quot;&quot;&quot;    queries shape: (b,1,e) : (batch_size, feasign_size, embedding_size)    keys shape: (b,f,e) : (batch_size, feasign_size, embedding_size)    &quot;&quot;&quot;    queries = tf.tile(queries, [1, keys.shape[1], 1]) # (b,1,e) -&gt; (b,t,e)    din_input = tf.concat([queries, keys, queries-keys, queries*keys], axis=-1)    din_layer1 = tf.layers.dense(din_input, layer_size[0], activation='sigmoid', name='att1')    dice_layer = self.dice(din_layer1, name='dice_layer') # above activation should be none    din_layer2 = tf.layers.dense(dice_layer, layer_size[1], activation=None, name='att2')    output = din_layer2 * keys    return output</code></pre><h2 id="DIN">DIN</h2><pre><code class="language-python">class DINModel:    def __init__(self, layer_size=(32,128), mom_decay_rate=0.99):        self.mom_decay_rate = mom_decay_rate        self.layer_size = layer_size        self.initializer = tf.contrib.layers.xavier_initializer()    def dice(self, _x, name=''):        alpha = tf.get_variable('dice_alpha'+name, _x.shape[-1], initializer=tf.contrib.layers.xavier_initializer(), dtype=tf.float32)        _x_norm = tf.layers.batch_normalization(inputs=_x, momentum=self.mom_decay_rate, name=&quot;dice_norm&quot;+name)        _x_p = tf.nn.sigmoid(_x_norm)        return alpha * (1.0 - _x_p) * _x + _x_p * _x    def layer_normalize(self,is_ln, inputs, mom_decay_rate, name):        if is_ln:            layer_norm = CustomLayerNormalization()            return layer_norm(inputs=inputs)        else:            return inputs    def din(self, queries, keys, is_bn):        &quot;&quot;&quot;        queries shape: (b,s,1,e) : (batch_size, slots_size, feasign_size, embedding_size)        keys shape: (b,s,f,e) : (batch_size, slots_size, feasign_size, embedding_size)        &quot;&quot;&quot;        queries = self.layer_normalize(is_bn, queries, self.mom_decay_rate, &quot;queries_norm&quot;)        keys = self.layer_normalize(is_bn, keys, self.mom_decay_rate, &quot;keys_norm&quot;)        queries = tf.tile(queries, [1, 1, keys.shape[2], 1]) # (b,s,1,e) -&gt; (b,s,t,e)        din_input = tf.concat([queries, keys, queries-keys, queries*keys], axis=-1)        din_layer1 = tf.layers.dense(din_input, self.layer_size[0], activation='relu', name='layer1', kernel_initializer=self.initializer, bias_initializer=self.initializer)        din_layer2 = self.dice(            tf.layers.dense(din_layer1, self.layer_size[1], activation=None, name='layer2', kernel_initializer=self.initializer, bias_initializer=self.initializer)            )        din_layer3 = tf.layers.dense(din_layer2, 1, activation=None, name='layer3', kernel_initializer=self.initializer, bias_initializer=self.initializer)        # b, s, t, 1        din_score = tf.reshape(din_layer3, [keys.shape[0], keys.shape[1], 1, keys.shape[2]])        output = tf.reduce_sum(din_score * keys, axis = 2) # b, s, e        # output = tf.matmul(din_score, keys) # b, s, t, e        return output    def __call__(self, queries, keys, is_bn=True, name='din'):        with tf.variable_scope(name):            out = self.din(queries, keys, is_bn)        return out</code></pre><h2 id="MMOE">MMOE</h2><pre><code class="language-python">gate_hidden1 = 128expoert_hidden = 512experts_nums = 4targets_nums = 3# input = b, eexperts_out = []for i in range(experts_nums):    expert_out = tf.layers.dense(input, expert_hidden, activation='relu')    experts_out.appned(expert_out)all_experts_out = tf.stack(expert_out, axis=1)def gate_build(input):    gate_hidden = tf.layers.dense(input, units=[gate_hidden1, experts_nums], activation='relu')    gate_out = tf.layers.dense(gate_hidden, experts_nums, activation='softmax')    gate_out = tf.expand_dims(gate_out, axis=1) # b, 4, 1    return gate_outouts = []# b, 4, 512for i in range(targets_nums):    gate = gate_build(input)    outs.append(tf.reduce_sum(gate * all_experts_out, axis = 1))# out shape [b, 512] * targets_nums</code></pre><h2 id="Senet">Senet</h2><pre><code class="language-python"># 64 * 100 * 8# reduce mean# 64 * 100 * 1  = 64 * 100# 2层mlp（relu， sigmoid）# 64 * 100 * 1# 哈达玛积input = tf.random.uniform(shape=(64, 100, 8))squ_tensor = tf.reduce_mean(input, axis=-1) # input b * l * e, b * lout1 = tf.layers.dense(squ_tensor, units=512, activation='relu', name='se1')out2 = 2 * tf.layers.dense(out1, units=squ_tensor.shape[-1], activation='sigmoid', name='se2‘) # b * lres = tf.expand_dims(out2, axis = -1) * input</code></pre><h2 id="梯度">梯度</h2><pre><code class="language-python">def get_gradients(qs, input_feas, embedding_size, rv_feas_len):    grad_list = []    # 处理一个batch内的梯度信息    def grad_processing(grad, emb_size, rv_len):        # batch内取均值 然后取绝对值        # 梯度绝对值的大小反应梯度对相关特征的敏感程度，越接近0 敏感程度越低        batch_avg_grad = tf.abs(tf.reduce_mean(grad, axis=0))        rv_grad = batch_avg_grad[:rv_len]        slot_embed_grad = tf.reshape(batch_avg_grad[rv_len:], [-1, emb_size])        # 取一个slot内取最大的绝对值代表该slot中的梯度        slot_grad = tf.reduce_max(slot_embed_grad, axis=1)        return tf.concat([rv_grad, slot_grad], axis=-1)    for i in range(len(qs)):        concat_grad = tf.concat(tf.gradients(qs[i], input_feas), axis=-1)        grad_list.append(grad_processing(concat_grad, embedding_size, rv_feas_len))    return grad_list</code></pre><h2 id="计算auc">计算auc</h2><pre><code class="language-python">import numpy as npfrom sklearn.metrics import roc_auc_score# python sklearn包计算aucdef get_auc(y_labels, y_scores):    auc = roc_auc_score(y_labels, y_scores)    print('AUC calculated by sklearn tool is {}'.format(auc))    return auc# 方法1计算aucdef calcAUC_byProb(labels, probs):    N = 0           # 正样本数量    P = 0           # 负样本数量    neg_prob = []   # 负样本的预测值    pos_prob = []   # 正样本的预测值    for index, label in enumerate(labels):        if label == 1:            # 正样本数++            P += 1            # 把其对应的预测值加到“正样本预测值”列表中            pos_prob.append(probs[index])        else:            # 负样本数++            N += 1            # 把其对应的预测值加到“负样本预测值”列表中            neg_prob.append(probs[index])    number = 0    # 遍历正负样本间的两两组合    for pos in pos_prob:        for neg in neg_prob:            # 如果正样本预测值&gt;负样本预测值，正序对数+1            if (pos &gt; neg):                number += 1            # 如果正样本预测值==负样本预测值，算0.5个正序对            elif (pos == neg):                number += 0.5    return number / (N * P)# 方法2计算auc, 当预测分相同时，未按照定义使用排序值的均值，而是直接使用排序值，当数据量大时，对auc影响小def calculate_auc_func2(y_labels, y_scores):    samples = list(zip(y_scores, y_labels))    rank = [(values2, values1) for values1, values2 in sorted(samples, key=lambda x:x[0])]    pos_rank = [i+1 for i in range(len(rank)) if rank[i][0] == 1]    pos_cnt = np.sum(y_labels == 1)    neg_cnt = np.sum(y_labels == 0)    auc = (np.sum(pos_rank) - pos_cnt*(pos_cnt+1)/2) / (pos_cnt*neg_cnt)    print('AUC calculated by function2 is {:.2f}'.format(auc))    return aucif __name__ == '__main__':    y_labels = np.array([1, 1, 0, 0, 0])    y_scores = np.array([0.4, 0.8, 0.2, 0.4, 0.5])    get_auc(y_labels, y_scores)    calculate_auc_func1(y_labels, y_scores)    calculate_auc_func2(y_labels, y_scores)</code></pre>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;FM&quot;&gt;FM&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class FactorizationMachine(tf.keras.Model):
    def __init__(self, num_features, embedding_size):
        super(FactorizationMachine, self).__init__()
        self.num_features = num_features
        self.embedding_size = embedding_size
        
        # 初始化参数
        self.w0 = tf.Variable(tf.zeros([1]), name=&#39;bias&#39;)
        self.w = tf.Variable(tf.random.normal([num_features, 1]), name=&#39;linear_weights&#39;)
        self.V = tf.Variable(tf.random.normal([num_features, embedding_size]), name=&#39;interaction_weights&#39;)

    def call(self, inputs):
        # embedding 部分
        embedding_first = tf.nn.embedding_lookup(self.w, inputs)
        embedding = tf.nn.embedding_lookup(self.V, inputs)
        value = tf.reshape(inputs, [-1, self.num_features, 1])
        embedding_value = tf.multiply(embedding, value)
        
        # 前向传播
        # first order 部分
        first_order = tf.reduce_sum(tf.multiply(embedding_first, value), axis=1)
        liner = tf.add(self.w0, first_order)
        
        # second order 部分
        mul_square = tf.square(tf.reduce_sum(embedding_value, 1))
        square_mul = tf.reduce_sum(tf.square(embedding_value), 1)
        second_order = 0.5 * tf.reduce_sum(tf.subtract(mul_square, square_mul), 1, keepdims=True)
        
        # FM 模型的预测值
        fm_model = tf.add(liner, second_order)
        return tf.nn.sigmoid(fm_model)
&lt;/code&gt;&lt;/pre&gt;
    
    </summary>
    
      <category term="算法" scheme="http://alexjiangzy.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="机器学习" scheme="http://alexjiangzy.com/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>信息流推荐算法小结</title>
    <link href="http://alexjiangzy.com/2021/10/23/%E4%BF%A1%E6%81%AF%E6%B5%81%E6%8E%A8%E8%8D%90%E8%A6%81%E7%82%B9/"/>
    <id>http://alexjiangzy.com/2021/10/23/信息流推荐要点/</id>
    <published>2021-10-22T16:12:44.000Z</published>
    <updated>2024-08-11T04:36:40.579Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="54256a170654d74619d1990b43d0b2bdc1142c031257c5955979977c2edb1cea">6a5a7cb0741f86ca05c133497452bba0a07ed99a733f8ed530dcf4ea2fe835502089a7c1c455cb3827a6078802128990f54ad8143f57e58ea37998f0275877ee49eb69b7b35bcb76d088234bfd1dbd2bba415b22e193f8144d10288efa03ef2c77f37bdd3d81fccab793d396ce8a6ff3ee2b74fc593114bdd4c705ece2dd6a44ec36a203af8169e51e7f3a73f49648dc5606514fcc8bc65da0fd776cae5194afdbeae5d17c33be0ea73c8b42c6ef6a75121b82da3e5d23cd05b803f890130dc536c6597198a6b3c74ca710ccb3d90ec7a4075a279f0836758bd1d07f02ea4bd709261e97ebd92822f002c03ba56b821ca0fa4f69eb6c680ad6494ec3424fbad3290d72168f661ee90eebd8274b4da0b0d842388115c195d0f2004843b25c676db9a896957f1b62b069c986ea244cd8270d421da0f8bcfc67c6e8ddaab9ef66f8c2e0558b1faccb3e135bc112b3d6b8bb54e20c7677030970107cf0bd3b03ab23e7ce9ad71a68ad942963cb3886f358c3fb6627ace148154349a343e2399d822b110887030d2f4ba29cca7f7b6867d0170f2c97b72dfebe958dc2090ccba20e63f305d0a8ea26b90edd9fdb57af56d8f2fcef63283d49f15e9532a549b8196bdc1a42a180b23f7c072f9c9dfc40fab448b45da8b4be860fb5afb5db7af948f8a5255dcf9d32b13b1ad84a684a91cdadbd9ba2e7fa0026e7e74ee9a5972098b2b21700ef5b02768cf945aeb0fa60694d10607f06c30e77011de03e82f956c23d0e0087ec9ce66008679c7c559354743817329da9d251a007c9107528de92b106ae2e0d44f4086ab2ed375a538fdab52f8d869e384648547a0546295e71800e1aff9d3dc20fb61bca80399b5cc48b29fd120d5d5012cf8d671a2354ca78af13634305951821479222e4561c4bfd05a59eb6cf54f7de48ee20123e1aa6007b1f1290f4faa56c21607395a9af8c41cb0a8e1dd5eacac61025cb211171ef83a862a4d8db614ad80a39dfe4f5556250df2a9ff74ff37e25a07171505947ebc8d3aff6546546c642e2d6076a9d5cf99d194ccd1c01dc58f12f6a9d44a3969ef620f6932dff6ff4f2125abd3e569031506d70b66c2af87daa97339915f50554add9eef6cd57e9e5468be896f881a45dbc98b54ad36ecf8f97b2966887fbf72eb4a381b5a94adcc7c5a31a974d128db7286ef014acf154dd2fd6da25e28bb43690bd8a19e007397d6068c2164e08ff96839f59e826a455f004613bfbef3c26982c7827db250bfb676176bc8ba1e310c914cd21b497b6eeb826c1d5fbd60c3304c9a6a8ee470ce3c384c0f4910c7723bd0561b1c947ee306b8b3248d5176ab9922410f0a72bddca3c1835a3a32ec2e4849a6d917649cb8374ab247076b12486274a493351adac02e5b1d8000878f5b156b8b4fc2387e76d31e9bc36f417b5508192545af0cf6d44e18b853d505071f04ee2212894d405c7d89020476280042080cac46ed8f8e55317feb71485f3fb13b3f957b170d086108db146f65fa3d2bff763e9f77c5d256da5b83b09b04772c006196658b9dbff6d4560ace8b5d7bf8bb87c272c91ea8e171a605122acabea701940cc327afacf52651b3480a66f548377cd1508eaf298a0010a2a9c797f11b8eac9003deb4d62a9c906c8453c88cc719e79c957dedba1a9eaf5764cde4b37b549fe8eb96d925ece63f2161f44ef7ac1ee2da9e0541643e0c737fc485fb448c4ec15fc00786f1416314fcafcca4d621dc9ddd4e3250ad80d9ce4a6c3ff8ec2842d6e96b8ca87d92151fe7554680df90604f114f8baa9a6f8636a3d8028174b93613674b7e62de0f24888dcfd7cf0eabc6be5aa6dfc153f42686b5fc7d1a135482d6d95cd37f21180fc4d391e288bd7a8772a7c915e3b92193b93f0dc4201de0b73dc3bb94ac4e061d4fcf78e4ee1d223887647ca4640bbf487a4617b0bfd7d7e53d7c6a6976a7898d470ec11549120f36f467afb7c786e7e4ababa66cd355677095d2e2dc24478f36115fc4cc09d1c09923f4b681fcb38556e60e6038efaf308253dfadeadd75b6e4865f73cdb60e3519a75bad3fa948dd30446f59f533dcb79ba3e4f320053a925ad21cff940ffbd5cbdcd5a6c6326ee26a27f221093122df6c2db3f76bd800c62066d662e6f371e9f8073e4c4c42321b94e01109708a697908e2b17915a04f20c179ae0618ccaf3ad756e92dcb5dfbc1e9bfc95b96586c7195fef0db3f3600918e1362be1807c76ffc68b34b4517cc19eb99fbdfc1240ef69afd60239035f71a08904f0019861942c75c7f8d05a06bb82a1aca0eb2905077d1d58c1038a9b6c0fe3888042354b26935d60ecf0bf9cf3ae7cb66f23f7cdb269b7238233945f5c0a0e33300bbb16bff0e836850e9608fb2c5555633a72eace4b8951c7cda7f1ef3628341718471dfa2fe86e04fb95e04f6332bc2ad0ef415c45bda8748b3ffb69e7273b0c3858d52f438281ffb81b9a4d451382b046c781736f11da950203c11cd48913177f4a95d7abcc0f7cd252822f3f645ee67ae319ff259e5f457b88507558af7b1faa8d033db4091ba0f1994932f7ded56738a7204c0961515988f7012c5660ed35827457cea9406f407cba3aca22b7f018cd3c17ce480c218616d7b7bcacf46ada7a05a694440e2c6b21dbd37886fe988b50cdba3fb71cfeac3432c945471e580b0d8544cfd4558c8ffc9cb97c6fa8ca988ff588db82a6333053cea667d0147530aa157ef36577852e78f1c275bec6a47b9dd0bef4c9131b9e94e3722c19fb4440ffdfe2e68b9c3df9b11fa524121a5c8f7cde854f88c43c3aeaba747899fa58a12f2ab43be2c2704c947f6d995ec3732821bc774b099e3370497cdf66a4ae4d517f4274e77ced5484d725efd717470bad031c84e939fc9ba9da00ccafbf60bb6f9edbcaff4a6882b19570333e6ba4e29b8bcef0c063744194aaeb984957397d78616ec3c6120f05a56844a65db815af2fadd4c1b5530380c5d4ccdec8ed72fe27f82dcda4b68293bed5a6f31bb22b746f4fd3dfd0a62b11c6e0fbdd1427abb5c5608e06ab717065874a4020a30ca69e8d5cb9101969aa28ec37396644326732884202ff96a1ad90fadbc5102e9d36cb8a3064428faa1814f7905f79ee7dbb42c0b7a4348f05738f47474505a8547a6d0f22b8ffb406a60f41879e351217e5f129e6535f70f0e3f8c046cd56505756a5f665848326657507bd68873804738ece624fcbdebafd801dc8c987a56f5a9eface1516001fe2e6c06b3122e399865536f3a709326bf28cb57f823250ae933fe30a30b34bb2c6cf11c77523dc71e5598b02505535fcb3350b702ec4b6e1a73081be7339add2de774de5cbe08966532aec0de744243eacfeb17f1b19a365f77b2d1e09e6a86169ea9e6715d14ca21d10cadcca2ff57b1915b70e42071b27fa737baea55f21ef3618a1dcd2e7e2a07ac33f543126aa85c4655dd1cd265301a771b528c9a16a9d8420ef930c1c2c9c2e466a52fd1b982c42f82ec9dba50b16ea2cf6fd8ced42f27f3203cdd6119ad8c4a0f058518c98797ab5773b47a0a5bcc77a42eeb9f6956b04944d336f337cb61303067114e24203b13827dd4f0a6bfa00c2214629348698d3db63c072d1751b2a3cd45efcb3c67144781813cfa84ac5e78520931e54f3c75f10865cd5ca8ec37162a42f693e65f8cda44a07aa1fed1aad2cc8ef97822a823484a42d834c68b285a845240ed9acb650f69ca809117857fb135aea17742a1fa2c55ac4320149983d488fdebe28c296b3f22862190a6b80b5cb700516d47c0e83b7f70cbd91bb3bcc2f53d7286cd67e4cdfbb1b577d90286a3aa8a5dbe8a35a986723513b11e0018e3777489082514776be7be2d82b79c4a0d714a2fbd253f3eea72ef46176dd284c03146cd16fac895acda2999c14a0ed07709b5ef58fcdc84f927c9da150b669de0fc7dda33e2fa7875ba49b4591a7faca8ab47518c2d753ea1fff41f8d5cae533505473daf19295a88631c6aa6cfc965c46492b37ee0333bfb9bd7246f0847f9a6a6dd184e494d04ad7db649e98210951c081245e9de2367b4b25dbcb586f0b5aa5d4b8796e46ae2e2d36b0a1a5b7758a342c01b0f812f89c50d3d7a4bbdad47da56833bf74a55184179a263ccde50b37d377ddb8a04ace6fb94639964ed6085026a6d7190f6ca6a7c75d615cb9d7159cc1983a1fa79d30dd53a4bafe750b0278cab297abad9177a6eb8400097caf2cde47233139809fda9ae9d82522660630cb2459fd994a7830511fc787e5d0b1fc50eed15783662b2d0fdde8c7792ac13a45d42c29d2cc7e35c938dd19464ee59c62c9fe38f892d2a5cf3ac97b7ed636330bc2c361e211e7ff5a2019f4dc14df9e8cf9140c246aa488132a8424b0393b33afe729013bba6581d3aa0cb15ee140a47102857e355e36089897b5d65bc27a9080255ec1d48b0f7a04699a4fdc9416547e3fccc7bf607a19fc307d2ad34a174af157084ae04f165a79a579abe18eecb4dcfcf98cac96c7f5729fdc7b69e1b8fcec3a6729692227e374daba9d11acda081e509bb5f82d12bbdb00b7fac1090ca2f8b808b93a6e82597019bef304cf8ae6ed0596a3948a4e35bd4100bd8760a2282fe227b3c4195af781e12f2f3bc0cdb60052f95286641103c820acdbb63b9a45eddfb15820f08ed5476f4a45c018dd8a02f1d482be6f32079dbff26df879de53f0f6f8226dcd0858c0b23311e9dc7b88277f3f57292143bdf22c64c1c7d7fc22d39ca951f90d396946de64c761d88692ace14176cb3b335e2f188e9c4642317aa00d94b5f31a4a5c4f748f5f248cbd344360fd74e085ce0a3a73346276747e7443271c15b8758e6a1fa7d89d8a30a8089785831fb45b382b5926711dd8686c5721d0e693eb6a23b43278e2877d2d762d0443c8a4d106ab7cbfe893a87be7565a30bb6bc7b154c1e978dadb3db18797095406279b6f6b0d35807ce7af4c97934f657ae098e0b40a2969addcf45d7b0d8b8d432d17a3de80522cc81886a8a90bb7ac66a04440bddd3dfcbd3a399aa7c98d6a7a01b7a411543381f33bcb4e8762ca20e959116b9a5cdcd27a5f4b8a148ae4d890b31d585910c37f69e99406402b492650449481f679f6d45abbf1aa9239e8f33ca7f87ea0dd4f51d76a85d1a7eee2b2acf6e5db77ee6ca6569fd3f60b19adfdec7a0fc3c4a64d18548dd53a1b119acd3be2dbd885c3a1c824dfda5d32ca38205bb4aafc36098b3fce2349d5ac0ad54560fba334616987c4a4b14995a022f77c5569aff8b85f94960b12ec5664aa0827053cde9acb5b43a90e15755f0ee7cd432e0fc018b7fa4a1a7eb3a54b0af7eacd4ecbc609d9824dffa5a0ab885d18e6e52e3b847f928f94df1b058e9902fd3ac4325ed470e1e906ccfd0abb5d37cfe506633d29754dc4d1fec3fabb7b8c208c86274e6e9db1fe7ea4faa2ca30cef28ac259499ecd7105720a0cbb5a8e0d058811b04b8b6219d404d254ae71210132e7866abc7bc26eb9e82a989c366fbeffb838ed6696ca3acde61ba70675cb8f3566d46bb9c9bea02cc349d954fc62b28abc755a0a98b716c290ef46ecc7c7eab5e7019e66044cbe85a21f6852a1ac24e22b8c5e29c2f31a7ea9f5d05caa67bccac2b60bb5b7419d63caa5c6b4158e4e58cc5907b9f398b2088c43672047d02a5f8c7a253bd102090ff8c173ee21142ea14a57e3266fab00f7083af2c3e8792ae1cebed589020f00f2fe5e129b31d7145a350b0ae8441bc301fe5c5e65dafdd7f3761b97c010f0e1d66164ade673772de9350bd96aae9e5d67f1f5624f71a4970b33a4b65dc2cabc33ac1c815f68c29957bc39ec9df1781ef1c6f6704af931c5f52e71d91fe808112124633ed192dc0c651abb835e4bce33d973ae44cdab966180981ed192d4ba4fd8db7d52d8bfbdcbecd7457490317876a7bb83cf8d06a98bcdc3b8ad32fb144a162121483dc0e71df72e25a5df202d68dc22c161415667788fd34a0338bcb3affd65511cc62affc970968fdbe7e63276ad81828350a501b799dca6764c5c99b0811eaa09f8ced706ba2a017b9f61460acb42f92ebd1ecd8c521b621f38afaf18ea8bcaa8997e9e2bd9b331c7411bd0751b8bbca8b908dd5c98f820c2bb96fbb9030bea59aa7774b2b8b1eab34c7af327bcd416b6684dde225b7696937199cffa35805a7c92618fcd5d9be9e21fb32f1adbb170955b8be2d4e73db3c9bc985e8f70f0feea0c9f636900f9f4001f3f8f4a84d9847e094676dbb43972f8a5163be6a5fc8a4b92f5194b47b1394eb9db80295afc8f471dc47fcd15ee64e3bac246ce9ee2bdf6ccff8dec5a63c22255096e4eda613c691b43aafd8c130ffbb39cee09e470134443166107df96cf99bd3920d0f68e4fec16f2638f175a2e9ec713a042c1fa1e7760f4d262aa20f6ddb196cf50a022f98fe80d0c3f98adbb2475d17d394a972b4cdcda386f1d83d6239390ae0b30f669d297ba02c8e619fb5f328e4b211238564901dc6fdee6c775cb19a3f307c94bd9be2bdfe26d42371bbdc55d937de8cc815da50029dffaed8df1519eb161803c63b34180e406eeb99217eed0a316b2a492c2bb1d71f7381a101f1b6884835e7c63ae03d230ff4368dee10a1380cf0637c0734fc61d7bf084bc36b7723987f75bd85ec62811d0b6dc1a3bd915edbdf7c4770c5e4740d0c3f9894913e2f6a87726b8fa4bbbe5e37f26ae3eb84c45428069fccf873f0f1639be5c7193795d6ad86d7062db243619bfc92358410aa8f70c9cbb3f9ed854a1baed5c9c846b0d2d7ab0219873444288efba96f5f8557f8658570eaf170934102303d448823ad174dfe5fa3291d8b191343a77230fe14a1b1d002ed85f4a19ac3369e023c4720c3baf055b6ff1950683932ba58c4d11f86027f9e31f81718633c1440e66ccbe018fc270431131203c9b4a96e1672845f715a2599b26fea36a96467501436554fe11d0384936f5c5d8f58554a3e5959c9b28b674289a57bbd0077fdce7cdc17be30ce6c1bf262ef710022b6b1a389ba83a9fbfc612024d673295bdbaa9b0f8910bb88962813d065a177ff39aa1e3c22ee3a7750581fb385247409b412dd52ebcdc207de09d61e8aff37c1c1e9461ad2aecdaae7f976c62620651b88e37e16cfc027a5579a908964201e3ed44de4eb4464e2416592c03f72beb3265d51ec026dd07318d6857b440f3d62039d68d5387fc4992f239d518c79457c2dc8caabe401a7941e4e27bc16e419783970a0441cd766188e0055e8a9e520387fe64d89d95326bffb83ca54b69591417ea3beb83be33a34cb84e7d4632270571a6e4ab67dedbccd8a4e88e2fd55cf8bfcf79d309378dadf82e84b1ef335dca23635afa99c3164808fb8629d109df212a64a6f61a07fd6a7c1d9c3314827906e4dd4a503a1815060abdc0bfa0e93a5e2b8e063a4a59eb5c2649ee1dcfe5108835940f83a181e4cd1df3f7fb56202e93beeec52931ab9655fd2eadceaf2db3113a191ce5281c3c3e52d004fb78d1a7e0f3dd508ded3f87c8c83c35c6648fb7fa53d321bbb3e0d78dd211912de0927f1955a78d8e466697a8423148cfcfa9b38d07756a1052a866a91aae51d982adcae34697f450569bb2ae43f52e49c5df7a33ea59664d0ab1c3ab37cab70a51e3397463bedca3a5286bbb308563b7ec294b18c0fd03ab9d312f49b999cdc5b29c0c70c766174fb5adba4b142d903c6c23f6d301016adad4eb4f1971fcbeb55e9eb628363063de42871fa99f0536f0e5ded08dfed041bf9c160eb0b6839334274bddc9ae2c21fc0e82df17a28da67db0c32e76039d692e8fda90dbfe01938b0d5ac34d134c8cca3fd1e2748772ea55bcc904cc75e52739cad16e33146c5dc52d55a08ec8972f0046c0d9ae2c764269aaa7ef627bdcd2c6df1abab6c748b2df06f54797e718829e59b929421482431132a9df4530ecd751273e1d6b209a892aab679613f0aed3750a1c6cef903d6de4028e52d81d49b7d65e11f055041788495583ac15b9b42fc0ba84bb15d5a125e2ec6bc0180573ddb9312757dbe6f208fd9156e44dc0cc3ffe8b55733f34bc7fea76df1a692a870fd6aec79bb6ce7b666cb81aeb21ea2ba773addb8abb5c7b3c2d6a63a4828d04e4632c2d2e44cf59ea4e72f3e8a8f5f1eff126e56c995c8f42d7a8836437ff05bf2d54e7026abe62e327c23f8d655103d1bade0924fd95e487b35bb1e36e452e6cdfd660036142c76f4302d11e8b66d08d2b240d7653d671fb3501fe7629ee080a77630edefc76c5e48d4c94bf292f3c692190fadf52f05233193480ccfb726d5b5db8ff82f724027658a458b6439d4123de03ef7da6c77ec2ca1dc1d00850279045c2a2500e2ab8a48d10995e1e0eec072b41e2a80e3f25dcd86301a345f20a121a9a4241326b8a4c067b0770bd4b65fba3c3aa47879452b09fc3283c3934d1f661fafdd596d2abe6d62ec4225fa6f470665a36f313f0d7c02b030e9f5839dc4cac3a4753c4570a243ee2c47f8e7fce9496ee81696ec7623e3d6d2cb5c6d7dd4d770496f60ab26c2bb7d5319b57ee996613b1864acdf12afd60137e43281699411b9861d161c29c4c513174f8780f9e8ede3c7de4c7dcbcee2df693eab186bd9fd8921fa8e63141c507d28432abdee228a25e7133854575adf19ed8d8ee1f7883cb846ca61b25a53a518be060c30cc73bd4d3d27e242ca3eb09adfdcc5cfcde3c1e7c30ea4b6f5dce87981c66be768800ee292fe141a5ef5892111e4e72f3a94aaa8038048502e998bf3a6b5b9c2c3e7773ae2941ff013f00127eb9c4eb6d84fe8fdfcf17c2846dd156f2aedda46069fee689ff53933a0aa0f4182f836d1ffebb36ee78f14c78a2bd05cdd5ac68a7e828d5e936de72df032c46329fea44168dba01b6c5536de2124807221adcd98c016241f70a173781debaf9fbe9f6789cac16afe80455e95de8faa6d35d32116df5c524db6b5ca1d9b9688b0146092d72d656fef3c73e6d1b0b296adf231faa4d5b4afc317cfd814faa69d23c644bfd1bb550545b1158bb10102340b4adf6ac44b79de94a0d1a6a03ca7aa039bf7341a9b008deff7b678e7fd9b0efaff660095be8c4fad974921fe605f7be4ca400718e78c5969002f4aa11acffd3a059946b31bb33fdb714c687115d24c841eee69f44600b1949fe5c802d31023f6781d92ffdfba1853c682f36e4eb652f0223f289e46506882ae12f1e2d1d039f437c3e4d05a74767d2fa03fbe0c33c67fe5e01cb614e4d572c87ef33f387424917240cd31e81dc04539fcd3ff3224c83ea7527a7b8875d4eac7f12d9bc72a06e8be53e78be7f77b8bf6c39acc42a978bc46a87107c2d61f8bcee2d8c2e79a8863e0139e386c0c86697f85ee63bcdbcf9963f5853be277b847a9200f13b4c75bd9725d74ae388946643e2faf619b612a17188a42de9b730f254a9940faed042bb4ec85b865b664f8e748ddf1ebb2db4138938f51ff9164f458ae3069a115f3113bed1b68d73c200aa8481d4365dc1985fbf4b12ff45438db46ac24bf1da357f907bf2a00df928ff21d47e0b39469dfb3a39882158098dadb875980cb631d997c21d7333f7ef985dba28a2fbae0c1576d70a0669cc86f693cca7018086851f31951a4ebce1070a9d3b4395801733180ed0c3a0850e6449b0e87d07f4db00e96f9074d684be69ca26f7cf7b71a4b68510b9064680ae0ea3fa9a61f41ab7352b04022546d39d4e2bfce4708a92ead3a77e3d24f7ff90c6beea101e13bf1a59af90624b3efe4ba5a4fc0860f8c8b95a4509f40b84f8d45c2a80e691cef9a675eb5e47407e961478cae87173b219cfd0235c6bc3cdc62c9d673bba193d4799b6781de8dfc15b67f664ee391b4bdb58dc98d5601c74d7f04c3d</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    <summary type="html">
    
      Here&#39;s something encrypted, password is required to continue reading.
    
    </summary>
    
      <category term="算法" scheme="http://alexjiangzy.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="机器学习" scheme="http://alexjiangzy.com/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>分类总结</title>
    <link href="http://alexjiangzy.com/2021/07/25/%E5%88%86%E7%B1%BB%E6%80%BB%E7%BB%93/"/>
    <id>http://alexjiangzy.com/2021/07/25/分类总结/</id>
    <published>2021-07-24T16:12:44.000Z</published>
    <updated>2024-09-08T18:36:51.310Z</updated>
    
    <content type="html"><![CDATA[<h1>目录</h1><h2 id="双指针">双指针</h2><ul><li><input type="checkbox" id="checkbox0" checked="true"><label for="checkbox0"></label><a href="#p1">LC-1 2数求和</a><br>先带上index排序</li><li><input type="checkbox" id="checkbox1" checked="true"><label for="checkbox1"></label><a href="#p15">LC-15 三数之和</a><br>排序，3指针: i，left=i+1, right=len(nums)-1；i/left/right都需要考虑去重</li><li><input type="checkbox" id="checkbox2" checked="true"><label for="checkbox2"></label><a href="#p16">LC-16 最接近的三数之和</a><br>同，排序，3指针</li><li><input type="checkbox" id="checkbox3" checked="true"><label for="checkbox3"></label><a href="#p75">LC-75 颜色分类</a><br>荷兰旗，head,mid=0,tail=n-1, mid=0交换mid和head前进, mid=2交换mid和tail，tail-1, 不然mid+1</li><li><input type="checkbox" id="checkbox4" checked="true"><label for="checkbox4"></label><a href="#p11">LC-11 盛最多水的容器</a><br>首尾指针，wide=j-i, height=min(h[i], h[j]), max(wide*height)</li><li><input type="checkbox" id="checkbox5" checked="true"><label for="checkbox5"></label><a href="#p763">LC-763 划分字母区间</a><br>转成字典，key:字母，value:[start, end]的index，合并区间模板</li><li><input type="checkbox" id="checkbox6" checked="true"><label for="checkbox6"></label><a href="#p80">LC-80 删除排序数组中的重复项</a><br>重复k个的问题。核心：if i &lt; k or nums[i] != nums[length - k]，重新组织数组</li><li><input type="checkbox" id="checkbox7" checked="true"><label for="checkbox7"></label><a href="#p581">LC-581 最短无序连续子数组</a><br>分段左、中、右，找到左和右的边界</li></ul><a id="more"></a><h2 id="动态规划">动态规划</h2><ul><li><input type="checkbox" id="checkbox8" checked="true"><label for="checkbox8"></label><a href="#p300">LC-300 最长上升子序列</a><br>i和j，初始化全长1，dp[i] = max(dp[i], dp[j]+1) if nums[i] &gt; nums[j]</li><li><input type="checkbox" id="checkbox9" checked="true"><label for="checkbox9"></label><a href="#p1143">LC-1143 最长公共子序列</a><br>二维dp，初始化 n+1 和 m+1, 对角线更新</li><li><input type="checkbox" id="checkbox10" checked="true"><label for="checkbox10"></label><a href="#p673">LC-673 最长递增子序列的个数</a><br>LIS变种，维护一个count计数器，</li><li><input type="checkbox" id="checkbox11" checked="true"><label for="checkbox11"></label><a href="#p5">LC-5 最长回文子串</a><br>二维dp，初始化全长，对角线单个字符，连续，左右缩进为true，更新最长length</li><li><input type="checkbox" id="checkbox12" checked="true"><label for="checkbox12"></label><a href="#p647">LC-647 回文子串</a><br>二维dp，初始化全长，对角线单个字符，连续，左右缩进为true，累加</li><li><input type="checkbox" id="checkbox13" checked="true"><label for="checkbox13"></label><a href="#p516">LC-516 最长回文子序列</a><br>二维dp，后向遍历</li><li><input type="checkbox" id="checkbox14" checked="true"><label for="checkbox14"></label><a href="#p221">LC-221 最大正方形</a><br>二维dp，初始化+1全长，和上左对角比较</li><li><input type="checkbox" id="checkbox15" checked="true"><label for="checkbox15"></label><a href="#p322">LC-322 零钱兑换</a><br>dp，初始化全长+1正无穷，序列长度是金额数，取或不取</li><li><input type="checkbox" id="checkbox16" checked="true"><label for="checkbox16"></label><a href="#p718">LC-718 最长重复子数组</a><br>二维dp，初始化+1全长，dp[i][j] = dp[i-1][j-1] + 1</li><li><input type="checkbox" id="checkbox17" checked="true"><label for="checkbox17"></label><a href="#p198">LC-198 打劫家舍</a><br>dp，偷和不偷，初始化全长为对应数字</li><li><input type="checkbox" id="checkbox18" checked="true"><label for="checkbox18"></label><a href="#p213">LC-213 打劫家舍2</a><br>dp，环形问题，化解成2个一般的问题</li><li><input type="checkbox" id="checkbox19" checked="true"><label for="checkbox19"></label><a href="#p62">LC-62 不同路经</a><br>二维dp，初始化全长，贴边为1，上和左求和</li><li><input type="checkbox" id="checkbox20" checked="true"><label for="checkbox20"></label><a href="#p63">LC-63 不同路径2</a><br>二维dp，初始化全长，外围一圈有障碍及余下就为0</li><li><input type="checkbox" id="checkbox21" checked="true"><label for="checkbox21"></label><a href="#p64">LC-64 最小路经和</a><br>二维dp，初始化全长，贴边初始化</li><li><input type="checkbox" id="checkbox22" checked="true"><label for="checkbox22"></label><a href="#p152">LC-152 乘积最大子数组</a><br>乘积需要维护最大和最小值</li><li><input type="checkbox" id="checkbox23" checked="true"><label for="checkbox23"></label><a href="#p523">LC-523 连续的子数组和</a><br>前缀和</li><li><input type="checkbox" id="checkbox24" checked="true"><label for="checkbox24"></label><a href="#p139">LC-139 单词拆分</a></li><li><input type="checkbox" id="checkbox25" checked="true"><label for="checkbox25"></label><a href="#p91">LC-91 解码方法</a></li><li><input type="checkbox" id="checkbox26" checked="true"><label for="checkbox26"></label><a href="#p264">LC-264 丑数2</a></li><li><input type="checkbox" id="checkbox27"><label for="checkbox27">LC-96 不同的二叉搜索树</label></li><li><input type="checkbox" id="checkbox28" checked="true"><label for="checkbox28"></label><a href="#p279">LC-279 完全平方数</a><br>dp，初始化为全长该位置数字，dp为减去平方数加1</li><li><input type="checkbox" id="checkbox29" checked="true"><label for="checkbox29"></label><a href="#p309">LC-309 股票买卖</a></li><li><input type="checkbox" id="checkbox30" checked="true"><label for="checkbox30"></label><a href="#p188">LC-188 买卖股票最佳时机</a></li><li><input type="checkbox" id="checkbox31" checked="true"><label for="checkbox31"></label><a href="#p343">LC-343 整数拆分</a></li><li><input type="checkbox" id="checkbox32" checked="true"><label for="checkbox32"></label><a href="#p416">LC-416 分割等和子集</a><br>除以2，转化为01背包问题</li><li><input type="checkbox" id="checkbox33" checked="true"><label for="checkbox33"></label><a href="#p42">LC-42 接雨水</a><br>双指针</li><li><input type="checkbox" id="checkbox34" checked="true"><label for="checkbox34"></label><a href="#p32">LC-32 最长有效括号</a><br>使用堆，堆内放-1，左括号压入，右括号直接弹出</li><li><input type="checkbox" id="checkbox35" checked="true"><label for="checkbox35"></label><a href="#p72">LC-72 编辑距离</a></li><li><input type="checkbox" id="checkbox36"><label for="checkbox36">LC-10 正则表达式匹配</label></li></ul><h2 id="树">树</h2><ul><li><input type="checkbox" id="checkbox37" checked="true"><label for="checkbox37"></label><a href="#p103">LC-103 二叉树锯齿层次遍历</a></li><li><input type="checkbox" id="checkbox38" checked="true"><label for="checkbox38"></label><a href="#p102">LC-102 二叉树的层序遍历</a></li><li><input type="checkbox" id="checkbox39" checked="true"><label for="checkbox39"></label><a href="#p107">LC-107 二叉树层序遍历2</a></li><li><input type="checkbox" id="checkbox40" checked="true"><label for="checkbox40"></label><a href="#p236">LC-236 二叉树最近公共祖先</a></li><li><input type="checkbox" id="checkbox41" checked="true"><label for="checkbox41"></label><a href="#p98">LC-98 验证二叉搜索树</a></li><li><input type="checkbox" id="checkbox42" checked="true"><label for="checkbox42"></label><a href="#p199">LC-199 二叉树的右视图</a></li><li><input type="checkbox" id="checkbox43" checked="true"><label for="checkbox43"></label><a href="#p101">LC-101 对称二叉树</a></li><li><input type="checkbox" id="checkbox44" checked="true"><label for="checkbox44"></label><a href="#p105">LC-105 从前序与中序遍历序列构造二叉树</a></li><li><input type="checkbox" id="checkbox45" checked="true"><label for="checkbox45"></label><a href="#p112">LC-112 路径总和</a></li><li><input type="checkbox" id="checkbox46" checked="true"><label for="checkbox46"></label><a href="#p113">LC-113 路径总和2</a></li><li><input type="checkbox" id="checkbox47" checked="true"><label for="checkbox47"></label><a href="#p124">LC-124 二叉树中的最大路径和</a></li><li><input type="checkbox" id="checkbox48" checked="true"><label for="checkbox48"></label><a href="#p129">LC-129 根到叶子节点数字之和</a></li><li><input type="checkbox" id="checkbox49" checked="true"><label for="checkbox49"></label><a href="#p958">LC-958 二叉树完全性检验</a></li><li><input type="checkbox" id="checkbox50" checked="true"><label for="checkbox50"></label><a href="#p104">LC-104 二叉树最大深度</a></li><li><input type="checkbox" id="checkbox51" checked="true"><label for="checkbox51"></label><a href="#p226">LC-226 反转(对称)二叉树</a></li><li><input type="checkbox" id="checkbox52" checked="true"><label for="checkbox52"></label><a href="#p114">LC-114 二叉树展开成链表</a></li><li><input type="checkbox" id="checkbox53" checked="true"><label for="checkbox53"></label><a href="#p230">LC-230 二叉搜索树中第k小的树</a></li><li><input type="checkbox" id="checkbox54" checked="true"><label for="checkbox54"></label><a href="#p662">LC-662 二叉树最大宽度</a></li><li><input type="checkbox" id="checkbox55" checked="true"><label for="checkbox55"></label><a href="#jz26">JZ-26 树的子结构</a></li><li><input type="checkbox" id="checkbox56"><label for="checkbox56">LC-1302 层数最深叶子节点的和</label></li></ul><h2 id="链表">链表</h2><ul><li><input type="checkbox" id="checkbox57" checked="true"><label for="checkbox57"></label><a href="#p206">LC-206 反转链表</a></li><li><input type="checkbox" id="checkbox58" checked="true"><label for="checkbox58"></label><a href="#p25">LC-25 K个一组反转链表</a></li><li><input type="checkbox" id="checkbox59" checked="true"><label for="checkbox59"></label><a href="#p92">LC-92 反转链表2</a></li><li><input type="checkbox" id="checkbox60" checked="true"><label for="checkbox60"></label><a href="#p21">LC-21 合并2个有序链表</a></li><li><input type="checkbox" id="checkbox61" checked="true"><label for="checkbox61"></label><a href="#p23">LC-23 合并k个排序链表</a></li><li><input type="checkbox" id="checkbox62" checked="true"><label for="checkbox62"></label><a href="#p141">LC-141 环形链表</a></li><li><input type="checkbox" id="checkbox63" checked="true"><label for="checkbox63"></label><a href="#p142">LC-142 环形链表2</a></li><li><input type="checkbox" id="checkbox64" checked="true"><label for="checkbox64"></label><a href="#p160">LC-160 相交链表</a></li><li><input type="checkbox" id="checkbox65" checked="true"><label for="checkbox65"></label><a href="#p19">LC-19 删除链表倒数第N个元素</a></li><li><input type="checkbox" id="checkbox66" checked="true"><label for="checkbox66"></label><a href="#p143">LC-143 重排链表</a><br>找中点；反转链表；合并有序链表</li><li><input type="checkbox" id="checkbox67" checked="true"><label for="checkbox67"></label><a href="#p234">LC-234 回文链表</a></li><li><input type="checkbox" id="checkbox68" checked="true"><label for="checkbox68"></label><a href="#p148">LC-148 排序链表</a></li><li><input type="checkbox" id="checkbox69" checked="true"><label for="checkbox69"></label><a href="#p82">LC-82 删除排序链表中的重复元素</a></li><li><input type="checkbox" id="checkbox70" checked="true"><label for="checkbox70"></label><a href="#p83">LC-83 删除排序链表中的重复元素2</a></li><li><input type="checkbox" id="checkbox71" checked="true"><label for="checkbox71"></label><a href="#p24">LC-24 两两交换链表中的节点</a></li><li><input type="checkbox" id="checkbox72" checked="true"><label for="checkbox72"></label><a href="#p328">LC-328 奇偶链表</a></li><li><input type="checkbox" id="checkbox73" checked="true"><label for="checkbox73"></label><a href="#p61">LC-61 旋转链表</a><br>设定偶链表头，奇偶链表参考: odd.next = even.next; odd = odd.next</li><li><input type="checkbox" id="checkbox74"><label for="checkbox74">LC-138 复制带随机指针的链表</label></li><li><input type="checkbox" id="checkbox75" checked="true"><label for="checkbox75"></label><a href="#p86">LC-86 分隔链表</a></li><li><input type="checkbox" id="checkbox76" checked="true"><label for="checkbox76"></label><a href="#p382">LC-382 链表随机节点</a></li></ul><h2 id="数学">数学</h2><ul><li><input type="checkbox" id="checkbox77" checked="true"><label for="checkbox77"></label><a href="#g1">高频-1 计算π</a></li><li><input type="checkbox" id="checkbox78" checked="true"><label for="checkbox78"></label><a href="#p69">LC-69 x 的平方根</a></li><li><input type="checkbox" id="checkbox79" checked="true"><label for="checkbox79"></label><a href="#p2">LC-2 两数相加</a></li><li><input type="checkbox" id="checkbox80" checked="true"><label for="checkbox80"></label><a href="#p8">LC-8 字符串转整数</a></li><li><input type="checkbox" id="checkbox81" checked="true"><label for="checkbox81"></label><a href="#p50">LC-50 Pow(x,n)</a></li><li><input type="checkbox" id="checkbox82" checked="true"><label for="checkbox82"></label><a href="#p50">JZ-16 数值的整数次方</a><br>递归，2个判断，正负幂，剩余n是否是2的倍数；进阶快速幂</li><li><input type="checkbox" id="checkbox83" checked="true"><label for="checkbox83"></label><a href="#p400">LC-400 第N个数字</a></li><li><input type="checkbox" id="checkbox84" checked="true"><label for="checkbox84"></label><a href="#p43">LC-43 字符串相乘</a></li><li><input type="checkbox" id="checkbox85" checked="true"><label for="checkbox85"></label><a href="#p470">LC-470 rand7() 实现 rand10()</a></li><li><input type="checkbox" id="checkbox86" checked="true"><label for="checkbox86"></label><a href="#p264">LC-264 丑数2</a></li><li><input type="checkbox" id="checkbox87" checked="true"><label for="checkbox87"></label><a href="#p279">LC-279 完全平方数</a></li><li><input type="checkbox" id="checkbox88" checked="true"><label for="checkbox88"></label><a href="#p12">LC-12 整数转罗马数字</a></li><li><input type="checkbox" id="checkbox89"><label for="checkbox89">LC-109 有序链表转换二叉搜索树</label></li><li><input type="checkbox" id="checkbox90"><label for="checkbox90">LC-369 单链表加一</label><br>计算中点</li><li><input type="checkbox" id="checkbox91"><label for="checkbox91">LC-41 缺失的第一个正数</label></li></ul><h2 id="滑动窗口">滑动窗口</h2><p>（子串一般都是是滑动窗口法，子串求和使用前缀和）</p><ul><li><input type="checkbox" id="checkbox92" checked="true"><label for="checkbox92"></label><a href="#p523">LC-523 连续的子数组和</a></li><li><input type="checkbox" id="checkbox93" checked="true"><label for="checkbox93"></label><a href="#p3">LC-3 无重复字符的最长子串</a></li><li><input type="checkbox" id="checkbox94" checked="true"><label for="checkbox94"></label><a href="#p395">LC-395 至少有k个重复字符子串</a></li><li><input type="checkbox" id="checkbox95" checked="true"><label for="checkbox95"></label><a href="#p76">LC-76 最小覆盖子串</a></li><li><input type="checkbox" id="checkbox96" checked="true"><label for="checkbox96"></label><a href="#p239">LC-239 滑动窗口最大值</a>(栈)</li><li><input type="checkbox" id="checkbox97" checked="true"><label for="checkbox97"></label><a href="#p209">LC-209 长度最小的子数组</a></li><li><input type="checkbox" id="checkbox98" checked="true"><label for="checkbox98"></label><a href="#p340">LC-340 至多包含k个不同字符的最长子串</a></li></ul><h2 id="二分查找">二分查找</h2><ul><li><input type="checkbox" id="checkbox99" checked="true"><label for="checkbox99"></label><a href="#p33">LC-33 搜索旋转排序数组</a></li><li><input type="checkbox" id="checkbox100" checked="true"><label for="checkbox100"></label><a href="#p162">LC-162 寻找峰值</a></li><li><input type="checkbox" id="checkbox101" checked="true"><label for="checkbox101"></label><a href="#p240">LC-240 搜索二维矩阵2</a></li><li><input type="checkbox" id="checkbox102" checked="true"><label for="checkbox102"></label><a href="#p718">LC-718 最长重复子数组</a></li><li><input type="checkbox" id="checkbox103" checked="true"><label for="checkbox103"></label><a href="#p31">LC-31 下一个排列</a></li><li><input type="checkbox" id="checkbox104" checked="true"><label for="checkbox104"></label><a href="#p230">LC-230 二叉搜索树中第k小的元素</a></li><li><input type="checkbox" id="checkbox105" checked="true"><label for="checkbox105"></label><a href="#p34">LC-34 排序数组中查找第一个和最后一个位置</a></li><li><input type="checkbox" id="checkbox106" checked="true"><label for="checkbox106"></label><a href="#p287">LC-287 寻找重复数</a></li><li><input type="checkbox" id="checkbox107" checked="true"><label for="checkbox107"></label><a href="#p153">LC-153 寻找旋转排序数组中的最小值（最大值）</a></li></ul><h2 id="回溯">回溯</h2><ul><li><input type="checkbox" id="checkbox108" checked="true"><label for="checkbox108"></label><a href="#p46">LC-46 全排列</a></li><li><input type="checkbox" id="checkbox109" checked="true"><label for="checkbox109"></label><a href="#p78">LC-78 子集</a></li><li><input type="checkbox" id="checkbox110" checked="true"><label for="checkbox110"></label><a href="#p77">LC-77 组合</a></li><li><input type="checkbox" id="checkbox111" checked="true"><label for="checkbox111"></label><a href="#p39">LC-39 组合总和</a></li><li><input type="checkbox" id="checkbox112" checked="true"><label for="checkbox112"></label><a href="#p22">LC-22 括号生成</a></li><li><input type="checkbox" id="checkbox113" checked="true"><label for="checkbox113"></label><a href="#p79">LC-79 单词搜索</a></li><li><input type="checkbox" id="checkbox114" checked="true"><label for="checkbox114"></label><a href="#p47">LC-47 全排列2</a></li><li><input type="checkbox" id="checkbox115" checked="true"><label for="checkbox115"></label><a href="#p40">LC-40 组合总和2</a></li><li><input type="checkbox" id="checkbox116" checked="true"><label for="checkbox116"></label><a href="#p17">LC-17 电话号码字母组合</a></li><li><input type="checkbox" id="checkbox117"><label for="checkbox117">LC-306 累加数</label></li></ul><h2 id="DFS-BFS">DFS/BFS</h2><ul><li><input type="checkbox" id="checkbox118" checked="true"><label for="checkbox118"></label><a href="#p200">LC-200 岛屿数量</a></li><li><input type="checkbox" id="checkbox119" checked="true"><label for="checkbox119"></label><a href="#p695">LC-695 岛屿最大面积</a></li><li><input type="checkbox" id="checkbox120" checked="true"><label for="checkbox120"></label><a href="#p1254">LC-1254 统计封闭岛的个数</a></li><li><input type="checkbox" id="checkbox121" checked="true"><label for="checkbox121"></label><a href="#p106">LC-106 从中序遍历和后序遍历构造二叉树</a></li><li><input type="checkbox" id="checkbox122" checked="true"><label for="checkbox122"></label><a href="#p547">LC-547 省份数量</a></li><li><input type="checkbox" id="checkbox123"><label for="checkbox123">LC-494 目标和</label></li><li><input type="checkbox" id="checkbox124"><label for="checkbox124">LC-337 打劫家舍3</label></li></ul><h2 id="字符串">字符串</h2><ul><li><input type="checkbox" id="checkbox125" checked="true"><label for="checkbox125"></label><a href="#p151">LC-151 反转字符串里的单词</a></li><li><input type="checkbox" id="checkbox126" checked="true"><label for="checkbox126"></label><a href="#p165">LC-165 比较版本号</a></li><li><input type="checkbox" id="checkbox127" checked="true"><label for="checkbox127"></label><a href="#p93">LC-93 复原IP地址</a></li><li><input type="checkbox" id="checkbox128" checked="true"><label for="checkbox128"></label><a href="#p227">LC-227 基本计算器2</a><br>使用单栈，注意数字</li><li><input type="checkbox" id="checkbox129" checked="true"><label for="checkbox129"></label><a href="#p443">LC-443 压缩字符串</a></li><li><input type="checkbox" id="checkbox130" checked="true"><label for="checkbox130"></label><a href="#p394">LC-394 字符串解码</a></li><li><input type="checkbox" id="checkbox131" checked="true"><label for="checkbox131"></label><a href="#p207">LC-207 课程表</a></li><li><input type="checkbox" id="checkbox132" checked="true"><label for="checkbox132"></label><a href="#p71">LC-71 简化路径</a></li><li><input type="checkbox" id="checkbox133"><label for="checkbox133">LC-556 下一个更大的元素3</label></li><li><input type="checkbox" id="checkbox134"><label for="checkbox134">LC-71 简化路径</label></li><li><input type="checkbox" id="checkbox135"><label for="checkbox135">LC-6 Z字形变换</label></li><li><input type="checkbox" id="checkbox136"><label for="checkbox136">LC-678 有效括号字符串</label></li><li><input type="checkbox" id="checkbox137"><label for="checkbox137">LC-97 交错字符串</label></li><li><input type="checkbox" id="checkbox138"><label for="checkbox138">LC-767 重构字符串</label></li><li><input type="checkbox" id="checkbox139"><label for="checkbox139">LC-179 最大数</label></li></ul><h2 id="其他">其他</h2><ul><li><input type="checkbox" id="checkbox140" checked="true"><label for="checkbox140"></label><a href="#p146">LC-146 LRU缓存机制</a></li><li><input type="checkbox" id="checkbox141" checked="true"><label for="checkbox141"></label><a href="#p54">LC-54 旋转矩阵</a></li><li><input type="checkbox" id="checkbox142" checked="true"><label for="checkbox142"></label><a href="#p215">LC-215 第K大的元素</a></li></ul><h1>双指针</h1><h3 id="a-name-p1-a-LC-1-2数求和"><a name="p1"></a> <a href="https://leetcode-cn.com/problems/two-sum/" target="_blank" rel="noopener">LC-1 2数求和</a></h3><p><code>双指针</code></p><p>给定一个整数数组 nums 和一个整数目标值 target，请你在该数组中找出 和为目标值 target  的那 两个 整数，并返回它们的数组下标。<br>你可以假设每种输入只会对应一个答案。但是，数组中同一个元素在答案里不能重复出现。<br>你可以按任意顺序返回答案。</p><p>Given nums = [2, 7, 11, 15], target = 9,<br>Because nums[0] + nums[1] = 2 + 7 = 9,<br>return [0, 1].</p><p>用一个dict来保存数据，key为数字，value为索引，代码如下：</p><pre><code class="language-python">class Solution:    def twoSum(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: List[int]        &quot;&quot;&quot;        mem = dict()                for index, item in enumerate(nums):            if item in mem:                return [mem[item], index]            else:                mem[target - item] = index</code></pre><p>双指针套路：</p><pre><code class="language-python">class Solution(object):    def twoSum(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: List[int]        &quot;&quot;&quot;        sn = sorted((num, i) for i, num in enumerate(nums))        i, j = 0, len(nums) - 1        print(sn)        while i &lt; j:            n, k = sn[i]            m, l = sn[j]            if n + m &lt; target:                i += 1            elif n + m &gt; target:                j -= 1            else:                return [k, l]</code></pre><p>这里将原数组，转化为(value, index)一个tuple类型。对这个tuple类型进行了排序，这样既保留了原有的index和value，又排了顺序。然后就是常规的步骤了，两个指针各指向一头，如果大于target就移动右指针，反之移动左指针。最近发现很多问题都有涉及对这个tuple的运用，真的是一个很高阶的技能。</p><h3 id="a-name-p15-a-LC-15-3数求和"><a name="p15"></a> <a href="https://leetcode-cn.com/problems/3sum/" target="_blank" rel="noopener">LC-15 3数求和</a></h3><p><code>双指针</code><br>给你一个包含 n 个整数的数组 nums，判断 nums 中是否存在三个元素 a，b，c ，使得 a + b + c = 0 ？请你找出所有和为 0 且不重复的三元组。<br>注意：答案中不可以包含<strong>重复</strong>的三元组。</p><p>输入：nums = [-1,0,1,2,-1,-4]<br>输出：[[-1,-1,2],[-1,0,1]]</p><pre><code class="language-python">class Solution:    def threeSum(self, nums: List[int]) -&gt; List[List[int]]:        ans = []        if len(nums) &lt;=2:            return ans        # sort array        nums.sort()        for i in range(len(nums)):            if nums[i] &gt; 0:                return ans            left = i + 1            right = len(nums) - 1            if (i &gt; 0 and nums[i] == nums[i-1]): # i 要考虑和前一个连续相同                continue            while left &lt; right:                if nums[i] + nums[left] + nums[right] &lt; 0:                    left += 1                elif nums[i] + nums[left] + nums[right] &gt; 0:                    right -= 1                else:                    ans.append([nums[i], nums[left], nums[right]])                    while left &lt; right and nums[left] == nums[left+1]:                        left += 1 # left考虑和后一个连续相同                    while right &gt; left and nums[right] == nums[right-1]:                        right -= 1 # right 考虑和前一个连续相同                    left += 1                    right -= 1        return ans</code></pre><p>依然是双指针经典模板，if elif else 三段式；要避开连续所以要考虑和后一位的关系，多利用while来做连续判断。</p><h3 id="a-name-p16-a-LC-16-最接近的三数之和"><a name="p16"></a> <a href="https://leetcode-cn.com/problems/3sum-closest/" target="_blank" rel="noopener">LC-16 最接近的三数之和</a></h3><p>给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数，使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。</p><p>示例：<br>输入：nums = [-1,2,1,-4], target = 1<br>输出：2<br>解释：与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。</p><p>排序+双指针</p><pre><code class="language-python">class Solution:    def threeSumClosest(self, nums: List[int], target: int) -&gt; int:        if len(nums) &lt;=3:            return sum(nums)        n = len(nums)        nums.sort()        res = sum(nums[:3])        for i in range(n-2):            left = i + 1            right = n - 1                        while left &lt; right:                ans = nums[i] + nums[left] + nums[right]                if ans &gt; target:                    right -= 1                elif ans &lt; target:                    left += 1                else:                    res = ans                    break                                if abs(ans-target) &lt; abs(res-target):                    res = ans        return res</code></pre><h3 id="a-name-p75-a-LC-75-颜色分类"><a name="p75"></a> <a href="https://leetcode-cn.com/problems/sort-colors/" target="_blank" rel="noopener">LC-75 颜色分类</a></h3><p>给定一个包含红色、白色和蓝色，一共 n 个元素的数组，原地对它们进行排序，使得相同颜色的元素相邻，并按照红色、白色、蓝色顺序排列。<br>此题中，我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。<br>示例 1：<br>输入：nums = [2,0,2,1,1,0]<br>输出：[0,0,1,1,2,2]</p><p>遍历一遍</p><pre><code class="language-python">class Solution:    def sortColors(self, nums: List[int]) -&gt; None:        &quot;&quot;&quot;        Do not return anything, modify nums in-place instead.        &quot;&quot;&quot;        # 荷兰国旗问题        n = len(nums)        start = 0        for i in range(0, n):            if nums[i] == 0:                nums[start], nums[i] = nums[i], nums[start]                start += 1                for i in range(start, n):            if nums[i] == 1:                nums[start], nums[i] = nums[i], nums[start]                start += 1</code></pre><p>荷兰国旗经典解法：</p><pre><code class="language-python">class Solution:    def sortColors(self, nums: List[int]):        &quot;&quot;&quot;        Do not return anything, modify nums in-place instead.        &quot;&quot;&quot;        # 荷兰国旗问题        head = 0        tail = len(nums) - 1        mid = 0        while mid &lt;= tail:            if nums[mid] == 0:                nums[head], nums[mid] = nums[mid], nums[head]                head += 1                mid += 1            elif nums[mid] == 2:                nums[mid], nums[tail] = nums[tail], nums[mid]                tail -= 1            else:                mid += 1</code></pre><h3 id="a-name-p11-a-LC-11-盛最多水的容器"><a name="p11"></a> <a href="https://leetcode-cn.com/problems/container-with-most-water/" target="_blank" rel="noopener">LC-11 盛最多水的容器</a></h3><p>给你 n 个非负整数 a1，a2，…，an，每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线，垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线，使得它们与 x 轴共同构成的容器可以容纳最多的水。<br>说明：你不能倾斜容器。</p><p>输入：[1,8,6,2,5,4,8,3,7]<br>输出：49<br>解释：图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下，容器能够容纳水（表示为蓝色部分）的最大值为 49。</p><p>双指针，首尾指针代表面积</p><pre><code class="language-python">class Solution:    def maxArea(self, height: List[int]) -&gt; int:        length = len(height)        max_area = 0        i = 0        j = length - 1        while i &lt; j:            wide = j - i            high = min(height[i], height[j])            area = high * wide            if area &gt; max_area:                max_area = area            if height[i] &lt; height[j]:                i += 1            else: j -= 1        return max_area</code></pre><h3 id="a-name-p763-a-LC-763-划分字母区间"><a name="p763"></a> <a href="https://leetcode-cn.com/problems/partition-labels/" target="_blank" rel="noopener">LC-763 划分字母区间</a></h3><p>字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段，同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。<br>示例：<br>输入：S = “ababcbacadefegdehijhklij”<br>输出：[9,7,8]<br>解释：<br>划分结果为 “ababcbaca”, “defegde”, “hijhklij”。<br>每个字母最多出现在一个片段中。<br>像 “ababcbacadefegde”, “hijhklij” 的划分是错误的，因为划分的片段数较少。</p><p>遍历一遍数组，建立字典，key为每个字母，value为首尾index，后将问题转化为合并区间问题。</p><p>合并区间模板：</p><pre><code class="language-python"># 动态维护merged = []intervals.sort()for interval in intervals:    if not merged or interval[0] &gt; merged[-1][1]:        merged.append(interval)    else:        # 动态修改最后一个 end index        merged[-1][1] = max(merged[-1][1], interval[1])</code></pre><pre><code class="language-python">class Solution:    def partitionLabels(self, s: str) -&gt; List[int]:        dic = {}        for i, v in enumerate(s):            if v in dic.keys():                st = min(dic[v][0], i)                ed = max(dic[v][1], i)                dic[v] = [st, ed]            else:                dic[v] = [i, i]        lt = list(dic.values())        # 排序是关键        lt.sort()        # 合并区间模板        merged = []        for interval in lt:            if not merged or interval[0] &gt; merged[-1][1]:                merged.append(interval)            else:                # 修改最后一个的第二位                merged[-1][1] = max(merged[-1][1], interval[1])        return [i[1] - i[0] + 1 for i in merged]</code></pre><h3 id="a-name-p80-a-LC-80-删除有序数组中的重复项-II"><a name="p80"></a> <a href="https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii/" target="_blank" rel="noopener">LC-80 删除有序数组中的重复项 II</a></h3><p>给你一个有序数组 nums ，请你 原地 删除重复出现的元素，使每个元素 最多出现两次 ，返回删除后数组的新长度。<br>不要使用额外的数组空间，你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。</p><p>示例 1：<br>输入：nums = [1,1,1,2,2,3]<br>输出：5, nums = [1,1,2,2,3]<br>解释：函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。</p><p>问题核心是只能采用原地模式，即不可以创造新的空间来保存数据。<br>对于有序数组，重复k个的问题。核心：<strong>if i &lt; k or nums[i] != nums[length - k]</strong></p><pre><code class="language-python">class Solution:    def removeDuplicates(self, nums: List[int]) -&gt; int:        length = 0        for i in range(len(nums)):            # 核心逻辑，排序数组和前n-k个元素比较，不重复保留，指针+1            if i &lt; 2 or nums[i] != nums[length - 2]:                nums[length] = nums[i]                length += 1        return length</code></pre><h3 id="a-name-p581-a-LC-581-最短无序连续子数组"><a name="p581"></a> <a href="https://leetcode.cn/problems/shortest-unsorted-continuous-subarray" target="_blank" rel="noopener">LC-581 最短无序连续子数组</a></h3><p>给你一个整数数组 nums ，你需要找出一个 连续子数组 ，如果对这个子数组进行升序排序，那么整个数组都会变为升序排序。<br>请你找出符合题意的 最短 子数组，并输出它的长度。<br>示例 1：<br>输入：nums = [2,6,4,8,10,9,15]<br>输出：5<br>解释：你只需要对 [6, 4, 8, 10, 9] 进行升序排序，那么整个表都会变为升序排序。<br>示例 2：<br>输入：nums = [1,2,3,4]<br>输出：0</p><pre><code class="language-python">class Solution(object):    def findUnsortedSubarray(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: int        &quot;&quot;&quot;        left = 0        right = 0        max = float('-inf')        min = float('inf')        for i in range(0, len(nums)):            if nums[i] &gt;= max:                max = nums[i]            else:                right = i         for i in range(len(nums) -1 , -1, -1):            if nums[i] &lt;= min:                min = nums[i]            else:                left = i        print(left, right)        if left == right:            return 0        else:            return right - left + 1</code></pre><h1>动态规划</h1><h3 id="a-name-p300-a-LC-300-最长上升子序列"><a name="p300"></a> <a href="https://leetcode-cn.com/problems/longest-increasing-subsequence/" target="_blank" rel="noopener">LC-300 最长上升子序列</a></h3><p><code>动态规划</code>, <code>递归</code></p><p>给你一个整数数组 nums ，找到其中最长严格递增子序列的长度。<br>输入：nums = [10,9,2,5,3,7,101,18]<br>输出：4<br>解释：最长递增子序列是 [2,3,7,101]，因此长度为 4</p><p><strong>转移方程：dp[i] = max{dp[i], dp[j]+1} if nums[i] &gt; nums[j]</strong></p><p>dp[i] 表示这一位上的最长子序列，对于[0, i-1]范围内所有的dp[j]，都需要得到最大</p><pre><code class="language-python">class Solution:    def lengthOfLIS(self, nums: List[int]) -&gt; int:        # 不连续        if len(nums) == 1:            return 1        dp = [1] * len(nums)        for i in range(len(nums)):            for j in range(i):                if nums[i] &gt; nums[j]:                    dp[i] = max(dp[i], dp[j] + 1)        return max(dp)</code></pre><h3 id="a-name-p1143-a-LC-1143-最长公共子序列"><a name="p1143"></a> <a href="https://leetcode-cn.com/problems/longest-common-subsequence/" target="_blank" rel="noopener">LC-1143 最长公共子序列</a></h3><p><code>动态规划</code>, <code>递归</code></p><p>给定两个字符串 text1 和 text2，返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ，返回 0 。<br>输入：text1 = “abcde”, text2 = “ace”<br>输出：3<br>解释：最长公共子序列是 “ace” ，它的长度为 3 。</p><p><strong>看到2个字符串，要想到2维的dp问题</strong><br><strong>转移方程：dp[i][j] = dp[i-1][j-1] + 1, 当 text1[i-1] = text2[j-1]</strong><br><strong>dp[i][j] = max(dp[i-1][j], dp[i][j-1]), 当 text1[i-1] != text2[j-1]</strong></p><p>状态初始化要考虑一个字符串是空串的可能性，<br>当 i = 0 时，dp[0][j] 表示的是 text1 中取空字符串 跟 text2的最长公共子序列，结果肯定为 0.<br>当 j = 0 时，dp[i][0] 表示的是 text2 中取空字符串 跟 text1的最长公共子序列，结果肯定为 0.<br>综上，当 i = 0 或者 j = 0 时，dp[i][j] 初始化为 0.</p><pre><code class="language-python">class Solution:    def longestCommonSubsequence(self, text1: str, text2: str) -&gt; int:        dp = [[0] * (len(text2) + 1) for i in range(len(text1) + 1)]        for i in range(1, len(text1) + 1):            for j in range(1, len(text2) + 1):                if text1[i-1] == text2[j-1]:                    dp[i][j] = dp[i-1][j-1] + 1                else:                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])                return dp[-1][-1]</code></pre><h3 id="a-name-p673-a-LC-673-最长递增子序列的个数"><a name="p673"></a> <a href="https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/" target="_blank" rel="noopener">LC-673 最长递增子序列的个数</a></h3><p><code>动态规划</code>, <code>递归</code></p><p>给定一个未排序的整数数组，找到最长递增子序列的个数。<br>输入: [1,3,5,4,7]<br>输出: 2<br>解释: 有两个最长递增子序列，分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。</p><p>LIS 问题，除了要知道最长递增子序列的长度以外，题目要知道有这个长度的子序列的数量。</p><p><strong>dp[i]：到nums[i]为止的最长递增子序列长度</strong><br><strong>count[i]：到nums[i]为止的最长递增子序列个数</strong></p><p><strong>if 在nums[i] &gt; nums[j] 分情况</strong><br><strong>if dp[j] + 1 &gt; dp[i]，说明最长递增子序列的长度增加了，dp[i] = dp[j] + 1，长度增加，数量不变 count[i] = count[j]</strong><br><strong>if dp[j] + 1 == dp[i]，说明最长递增子序列的长度并没有增加，但是出现了长度一样的情况，数量增加 count[i] += count[j]</strong></p><p>不断更新最大长度，最后返回所有dp[i] == maxlen 计数器的和</p><pre><code class="language-python">class Solution:    def findNumberOfLIS(self, nums: List[int]):        n = len(nums)        if n == 1:             return 1        dp = [1] * n        count = [1] * n        max_len = 0        for i in range(1, n):            for j in range(i):                if nums[i] &gt; nums[j]:                    # 正常情况下，我们直接用 dp[i] = max(dp[1], dp[j]+1)就好                    # 但是这里我们需要计数，所以拆成下面 if-else 的形式                    if dp[j] + 1 &gt; dp[i]: # 如果发现：当前长度 &gt; 历史最佳长度                        dp[i] = dp[j] + 1 # 更新dp[i]， 这一步相当于 dp[i] = max(dp[1], dp[j]+1)                        count[i] = count[j] # 更新计数器，结果+1，但是计数是不变的                    elif dp[j] + 1 == dp[i]: # 如果发现：当前长度=历史最佳长度                        count[i] += count[j] # 更新计数器            max_len = max(max_len, dp[i]) #找到当前i的位置下，最长的子序列长度        # 在计数器里面查找最长子序列的总次数        res = 0        for i in range(n):            if dp[i] == max_len:                res += count[i]        return res</code></pre><h3 id="a-name-p5-a-LC-5-最长回文子串"><a name="p5"></a> <a href="https://leetcode-cn.com/problems/longest-palindromic-substring/" target="_blank" rel="noopener">LC-5 最长回文子串</a></h3><p><code>动态规划</code>, <code>递归</code><br>给你一个字符串 s，找到 s 中最长的回文子串。</p><p>输入：s = “babad”<br>输出：“bab”<br>解释：“aba” 同样是符合题意的答案。</p><p><strong>转移方程：if dp[i-1][j+1] == True and s[i] == s[j], then dp[i][j] = True</strong><br>要求最长，则需要在循环的时候不断更新最长的长度，以及所对应的起始位置得到子串<br>注意：初始化矩阵为False的时候，需要把一些简单解先求出来。</p><pre><code class="language-python">class Solution:    def longestPalindrome(self, s: str) -&gt; str:        maxlen = 0        start = 0        end = 0        if len(s) &lt;= 1:            return s                dp = [[False] * len(s) for i in range(len(s))]        # j start i end        for i in range(0, len(s)):            for j in range(0, i + 1):                if i == j:                    dp[i][j] = True                elif i - j == 1 and s[i] == s[j]:                    dp[i][j] = True                elif dp[i-1][j+1] and s[i] == s[j]:                    dp[i][j] = True                else:                    continue                if i-j + 1 &gt; maxlen:                    maxlen = i - j + 1                    start = j                    end = i        return s[start:end+1]</code></pre><h3 id="a-name-p647-a-LC-647-回文子串"><a name="p647"></a> <a href="https://leetcode-cn.com/problems/palindromic-substrings/" target="_blank" rel="noopener">LC-647 回文子串</a></h3><p><code>动态规划</code>, <code>递归</code><br>给定一个字符串，你的任务是计算这个字符串中有多少个回文子串。<br>输入：“abc”<br>输出：3<br>解释：三个回文子串: “a”, “b”, “c”</p><p>输入：“aaa”<br>输出：6<br>解释：6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”</p><p>和LC5一样，区别在于，需要记录结果，且结果可以重复</p><pre><code class="language-python">class Solution:    def countSubstrings(self, s: str) -&gt; int:        if len(s) &lt;= 1:            return 1        dp = [[False] * len(s) for i in range(len(s))]        res = 0        for i in range(len(s)):            for j in range(0, i+1):                print(i,j)                if i - j == 0:                    dp[i][j] = True                    res += 1                    continue                if i - j == 1 and s[i] == s[j]:                    dp[i][j] = True                    res += 1                    continue                if dp[i-1][j+1] and s[i] == s[j]:                    dp[i][j] = True                    res += 1                    continue        return res</code></pre><h3 id="a-name-p516-a-LC-516-最长回文子序列"><a name="p516"></a> <a href="https://leetcode-cn.com/problems/longest-palindromic-subsequence//" target="_blank" rel="noopener">LC-516 最长回文子序列</a></h3><p><code>动态规划</code>, <code>递归</code><br>给你一个字符串 s ，找出其中最长的回文子序列，并返回该序列的长度。<br>子序列定义为：不改变剩余字符顺序的情况下，删除某些字符或者不删除任何字符形成的一个序列。</p><p>输入：s = “bbbab”<br>输出：4<br>解释：一个可能的最长回文子序列为 “bbbb” 。</p><p><strong>转移方程：if s[i] == s[j] then dp[i][j] = dp[i+1][j-1] + 2</strong><br><strong>if s[i] != s[j] then dp[i][j] = max(dp[i+1][j], dp[i][j-1])</strong></p><pre><code class="language-python">class Solution:    def longestPalindromeSubseq(self, s: str) -&gt; int:       if len(s) &lt;= 1:            return 1                dp = [[0] * len(s) for i in range(len(s))]        for i in range(len(s)):            dp[i][i] = 1        # i 是首，j 是尾，循环时i变小，j变大，        for i in range(len(s) - 1, -1, -1):            for j in range(i+1, len(s), 1):                if s[i] == s[j]:                    dp[i][j] = dp[i+1][j-1] + 2                else:                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])        return dp[0][-1]    </code></pre><h3 id="a-name-p221-a-LC-221-最大正方形"><a name="p221"></a> <a href="https://leetcode-cn.com/problems/maximal-square/" target="_blank" rel="noopener">LC-221 最大正方形</a></h3><p><code>动态规划</code>, <code>递归</code><br>在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内，找到只包含 ‘1’ 的最大正方形，并返回其面积。<br>输入：matrix = [<br>[“1”,“0”,“1”,“0”,“0”],<br>[“1”,“0”,“1”,“1”,“1”],<br>[“1”,“1”,“1”,“1”,“1”],<br>[“1”,“0”,“0”,“1”,“0”]<br>]<br>输出：4</p><p>dp数组中保存每个位置对应的最大正方形的边长<br><strong>转移方程：dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1</strong></p><pre><code class="language-python">class Solution:    def maximalSquare(self, matrix: List[List[str]]) -&gt; int:        m, n = len(matrix), len(matrix[0])        dp = [[0] * (n + 1) for _ in range(m + 1)]        for i in range(m + 1):            dp[i][0] = 0        for j in range(n + 1):            dp[0][j] = 0        for i in range(1, m + 1):            for j in range(1, n + 1):                if matrix[i-1][j-1] == '1':                    dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1                else:                    dp[i][j] = 0        return max([max(i) for i in dp]) ** 2</code></pre><h3 id="a-name-p322-a-LC-322-零钱兑换"><a name="p322"></a> <a href="https://leetcode-cn.com/problems/coin-change/" target="_blank" rel="noopener">LC-322 零钱兑换</a></h3><p>给你一个整数数组 coins ，表示不同面额的硬币；以及一个整数 amount ，表示总金额。<br>计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额，返回 -1 。</p><p>输入：coins = [1, 2, 5], amount = 11<br>输出：3<br>解释：11 = 5 + 5 + 1</p><p>dp存储各个金额<br><strong>转移方程：dp[i] = min(dp[i], dp[i - coin] + 1)</strong></p><pre><code class="language-python">class Solution:    def coinChange(self, coins: List[int], amount: int):        dp = [float('inf')] * (amount + 1)        # dp 存储的是总金额 这个要想到        dp[0] = 0        for i in range(amount + 1):            for coin in coins:                # 只有当硬币面额不大于要求面额数时，才能取该硬币                if coin &lt;= i:                    dp[i] = min(dp[i], dp[i - coin] + 1)        if dp[amount] &lt;= amount:            return dp[amount]        else:            return -1</code></pre><h3 id="a-name-p718-a-LC-718-最长重复子数组"><a name="p718"></a> <a href="https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/" target="_blank" rel="noopener">LC-718 最长重复子数组</a></h3><p>给两个整数数组 A 和 B ，返回两个数组中公共的、长度最长的子数组的长度。<br>示例：<br>输入：<br>A: [1,2,3,2,1]<br>B: [3,2,1,4,7]<br>输出：3<br>解释：<br>长度最长的公共子数组是 [3, 2, 1] 。</p><p><strong>转移方程：dp[i][j] = dp[i-1][j-1] + 1</strong></p><pre><code class="language-python">class Solution:    def findLength(self, nums1: List[int], nums2: List[int]) -&gt; int:        if not nums1 or not nums2:            return 0        a, b = len(nums1), len(nums2)        dp = [[0] * (a + 1) for _ in range(b + 1)]        for i in range(1, b+1):            for j in range(1, a+1):                if nums1[j - 1] == nums2[i - 1]:                    dp[i][j] = dp[i-1][j-1] + 1                # 子串需要严格相等                # else:                #     dp[i][j] = dp[i-1][j-1]        return max([max(dp[i][:]) for i in range(len(dp))])</code></pre><h3 id="a-name-p198-a-LC-198-打劫家舍"><a name="p198"></a> <a href="https://leetcode-cn.com/problems/house-robber/" target="_blank" rel="noopener">LC-198 打劫家舍</a></h3><p><code>动态规划</code>, <code>递归</code></p><p>你是一个专业的小偷，计划偷窃沿街的房屋。每间房内都藏有一定的现金，影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统，如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警。<br>给定一个代表每个房屋存放金额的非负整数数组，计算你 不触动警报装置的情况下 ，一夜之内能够偷窃到的最高金额。</p><p>输入：[1,2,3,1]<br>输出：4</p><p><strong>转移方程：dp[i] = max(nums[i] + dp[i-2], dp[i-1])</strong></p><pre><code class="language-python">class Solution:    def rob(self, nums: List[int]) -&gt; int:        if len(nums) &lt;= 1:            return nums[0]                dp = [i for i in range(len(nums))]        dp[0] = nums[0]        dp[1] = max(nums[1], nums[0]) # dp[1] 取 1 和 2 中的 max        for i in range(2, len(nums)):            dp[i] = max(nums[i]+dp[i-2], dp[i-1])        return dp[-1]</code></pre><h3 id="a-name-p213-a-LC-213-打劫家舍2"><a name="p213"></a> <a href="https://leetcode-cn.com/problems/house-robber-ii//" target="_blank" rel="noopener">LC-213 打劫家舍2</a></h3><p><code>动态规划</code>, <code>递归</code><br>你是一个专业的小偷，计划偷窃沿街的房屋，每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ，这意味着第一个房屋和最后一个房屋是紧挨着的。同时，相邻的房屋装有相互连通的防盗系统，如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警 。<br>给定一个代表每个房屋存放金额的非负整数数组，计算你 在不触动警报装置的情况下 ，今晚能够偷窃到的最高金额。</p><p>输入：nums = [1,2,3,1]<br>输出：4<br>解释：你可以先偷窃 1 号房屋（金额 = 1），然后偷窃 3 号房屋（金额 = 3）。<br>     偷窃到的最高金额 = 1 + 3 = 4 。</p><p><strong>转移方程：dp[i] = max(dp[i-2] + nums[i], dp[i-1])</strong></p><pre><code class="language-python">class Solution:    def rob(self, nums: List[int]) -&gt; int:        def sub_rob(nums,n) :            dp = [0] * n             dp[0] = nums[0]            dp[1] = max(nums[0],nums[1])             for i in range(2,n):                dp[i] = max(dp[i-2] + nums[i], dp[i-1])                   return dp[-1]         n = len(nums)        if n &lt;3:            return max(nums)        nums1 = nums[1:]        nums2 = nums[:-1]                result1 = sub_rob(nums1,n-1)        result2 = sub_rob(nums2,n-1)        return max(result1,result2)</code></pre><h3 id="a-name-p62-a-LC-62-不同路径"><a name="p62"></a> <a href="https://leetcode-cn.com/problems/unique-paths/" target="_blank" rel="noopener">LC-62 不同路径</a></h3><p><code>动态规划</code>, <code>递归</code><br>一个机器人位于一个 m x n 网格的左上角 （起始点在下图中标记为 “Start” ）。<br>机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角（在下图中标记为 “Finish” ）。<br>问总共有多少条不同的路径？</p><p>输入：m = 3, n = 2<br>输出：3<br>解释：<br>从左上角开始，总共有 3 条路径可以到达右下角。</p><ol><li>向右 -&gt; 向下 -&gt; 向下</li><li>向下 -&gt; 向下 -&gt; 向右</li><li>向下 -&gt; 向右 -&gt; 向下</li></ol><p><strong>转移方程： dp[i][j] = dp[i-1][j] + dp[i][j-1]</strong></p><pre><code class="language-python">class Solution:    def uniquePaths(self, m: int, n: int):        if m &lt;=0 or n &lt;= 0:            return 0        dp = [[0] * n for _ in range(m)]        # 外侧一圈要先初始化为1        for i in range(m):            dp[i][0] = 1        for j in range(n):            dp[0][j] = 1                for i in range(1, m):            for j in range(1, n):                dp[i][j] = dp[i-1][j] + dp[i][j-1]        return dp[-1][-1]</code></pre><h3 id="a-name-p63-a-LC-63-不同路径2"><a name="p63"></a> <a href="https://leetcode-cn.com/problems/unique-paths-ii/" target="_blank" rel="noopener">LC-63 不同路径2</a></h3><p><code>动态规划</code>, <code>递归</code><br>一个机器人位于一个 m x n 网格的左上角 （起始点在下图中标记为“Start” ）。<br>机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角（在下图中标记为“Finish”）。<br>现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径？</p><p>输入：obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]<br>输出：2<br>解释：<br>3x3 网格的正中间有一个障碍物。<br>从左上角到右下角一共有 2 条不同的路径：</p><ol><li>向右 -&gt; 向右 -&gt; 向下 -&gt; 向下</li><li>向下 -&gt; 向下 -&gt; 向右 -&gt; 向右</li></ol><p><strong>转移方程： dp[i][j] = dp[i-1][j] + dp[i][j-1]</strong><br><strong>if 有障碍物 then dp[i][j] = 0, 注意外圈有障碍物，则余下都为0，需要额外处理</strong></p><pre><code class="language-python">class Solution:    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -&gt; int:        if not obstacleGrid:            return 0        m = len(obstacleGrid)        n = len(obstacleGrid[0])        dp = [[0] * n for _ in range(m)]        # 外侧一圈要先初始化为1        for i in range(m):            if obstacleGrid[i][0] == 1:                for x in range(i, m):                    dp[x][0] = 0                break            else:                dp[i][0] = 1        for j in range(n):            if obstacleGrid[0][j] == 1:                for x in range(j, n):                    dp[0][x] = 0                break            else:                dp[0][j] = 1        for i in range(1, m):            for j in range(1, n):                if obstacleGrid[i][j] == 1:                    dp[i][j] = 0                else:                    dp[i][j] = dp[i-1][j] + dp[i][j-1]        return dp[-1][-1]</code></pre><h3 id="a-name-p63-a-LC-64-最小路径和"><a name="p63"></a> <a href="https://leetcode-cn.com/problems/minimum-path-sum/" target="_blank" rel="noopener">LC-64 最小路径和</a></h3><p><code>动态规划</code>, <code>递归</code></p><p>给定一个包含非负整数的 m x n 网格 grid ，请找出一条从左上角到右下角的路径，使得路径上的数字总和为最小。<br>说明：每次只能向下或者向右移动一步。</p><p>输入：grid = [[1,3,1],[1,5,1],[4,2,1]]<br>输出：7<br>解释：因为路径 1→3→1→1→1 的总和最小。</p><p><strong>转移方程：dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]</strong><br>注意贴边的一圈都只能从左到右，或者从上到下得到值</p><pre><code class="language-python">class Solution:    def minPathSum(self, grid: List[List[int]]) -&gt; int:        if not grid:            return 0                m = len(grid)        n = len(grid[0])        dp = [[0] * (n) for _ in range(m)]        dp[0][0] = grid[0][0]        for i in range(1, n):            dp[0][i] = dp[0][i-1] + grid[0][i]        for j in range(1, m):            dp[j][0] = dp[j-1][0] + grid[j][0]        for i in range(1, m):            for j in range(1, n):                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]        return dp[-1][-1]</code></pre><h3 id="a-name-p152-a-LC-152-乘积最大子数组"><a name="p152"></a> <a href="https://leetcode-cn.com/problems/maximum-product-subarray/" target="_blank" rel="noopener">LC-152 乘积最大子数组</a></h3><p>给你一个整数数组 nums ，请你找出数组中乘积最大的连续子数组（该子数组中至少包含一个数字），并返回该子数组所对应的乘积。<br>示例 1:<br>输入: [2,3,-2,4]<br>输出: 6<br>解释: 子数组 [2,3] 有最大乘积 6。</p><p><strong>转移方程：max_v = max(max_a * nums[i], nums[i], min_a * nums[i])</strong><br><strong>min_v = min(min_a * nums[i], nums[i], max_a * nums[i])</strong></p><p>乘积最大可能是负数乘负数，或者正数乘正数，所以需要维护2个队列</p><pre><code class="language-python">class Solution:    def maxProduct(self, nums: List[int]) -&gt; int:        # 乘积需要维护2个值，最大值和最小值        if len(nums) == 1:            return nums[0]        max_a = nums[0]        min_a = nums[0]        res = nums[0]        for i in range(1, len(nums)):            max_v = max(max_a * nums[i], nums[i], min_a * nums[i])            min_v = min(min_a * nums[i], nums[i], max_a * nums[i])            res = max(max_v, res)            max_a = max_v            min_a = min_v        return res</code></pre><h3 id="a-name-p523-a-LC-523-连续的子数组和"><a name="p523"></a> <a href="https://leetcode-cn.com/problems/continuous-subarray-sum/" target="_blank" rel="noopener">LC-523 连续的子数组和</a></h3><p><code>前缀和</code><br>给你一个整数数组 nums 和一个整数 k ，编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组<br>子数组大小 至少为 2 ，且<br>子数组元素总和为 k 的倍数。<br>如果存在，返回 true ；否则，返回 false 。<br>如果存在一个整数 n ，令整数 x 符合 x = n * k ，则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。</p><p>示例 1：<br>输入：nums = [23,2,4,6,7], k = 6<br>输出：true<br>解释：[2,4] 是一个大小为 2 的子数组，并且和为 6 。</p><p>看到子数组和求和问题的时候，要想到前缀和方法</p><pre><code class="language-python">class Solution:    def checkSubarraySum(self, nums: List[int], k: int) -&gt; bool:        # 前缀和+哈希表，查看是否有连续的一段元素的，和为k的倍数就行，同余定理，i%m - j%m = (i-j)%m        n = len(nums)        if n &lt; 2: return False        # 记录前缀和，除以k的余数        sub = [0] * (1 + len(nums))        for i in range(len(nums)):            sub[i+1] = (sub[i] + nums[i]) % k        # 记录相同前缀和对k取余，记录所有余数相同的位置list        dic = {}        for idx, pre in enumerate(sub):            print(pre)            if pre not in dic.keys():                 dic[pre] = [idx]            else:                dic[pre].append(idx)        # 查找是否存在一段， max - min &gt;= 2 来判断        for k, v in dic.items():            if max(v) - min(v) &gt;=2:                return True        return False</code></pre><h3 id="a-name-p139-a-LC-139-单词拆分"><a name="p139"></a> <a href="https://leetcode-cn.com/problems/word-break/" target="_blank" rel="noopener">LC-139 单词拆分</a></h3><p>给定一个非空字符串 s 和一个包含非空单词的列表 wordDict，判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。<br>说明：<br>拆分时可以重复使用字典中的单词。<br>你可以假设字典中没有重复的单词。<br>示例 1：<br>输入: s = “leetcode”, wordDict = [“leet”, “code”]<br>输出: true<br>解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。</p><p><strong>转移方程：dp[j] = dp[i] and s[i:j]</strong></p><pre><code class="language-python">class Solution:    def wordBreak(self, s: str, wordDict: List[str]) -&gt; bool:        n = len(s)        dp = [False] * (n + 1)        dp[0] = True        for i in range(n):            for j in range(i+1, n+1):                if dp[i] and s[i:j] in wordDict:                    dp[j] = True        return dp[-1]</code></pre><h3 id="a-name-p91-a-LC-91-解码方法"><a name="p91"></a> <a href="https://leetcode-cn.com/problems/decode-ways/" target="_blank" rel="noopener">LC-91 解码方法</a></h3><p>要解码 已编码的消息，所有数字必须基于上述映射的方法，反向映射回字母（可能有多种方法）。例如，“11106” 可以映射为：<br>“AAJF” ，将消息分组为 (1 1 10 6)<br>“KJF” ，将消息分组为 (11 10 6)</p><p>输入：s = “12”<br>输出：2<br>解释：它可以解码为 “AB”（1 2）或者 “L”（12）。</p><p><strong>递推公式：</strong><br>$$<br>\left{\begin{array}{l}<br>f[i]=f[i-1], 1 \leqslant a \leq 9 \newline<br>f[i]=f[i-2], 10 \leqslant b \leqslant 26 \newline<br>f[i]=f[i-1]+f[i-2], 1 \leqslant a \leq 9,10 \leqslant b \leqslant 26<br>\end{array}\right.<br>$$</p><pre><code class="language-python">class Solution:    def numDecodings(self, s: str) -&gt; int:        length = len(s)        dp = [0] * (length + 1)        dp[0] = 1        if 1 &lt;= int(s[0]) &lt;= 9:            dp[1] = 1        # 注意一位和两位的条件        for i in range(1, length):            if 1 &lt;= int(s[i]) &lt;= 9:                dp[i + 1] += dp[i]            if 10 &lt;= int(s[i - 1:i + 1]) &lt;= 26:                dp[i + 1] += dp[i - 1]        return dp[-1]</code></pre><h3 id="a-name-p264-a-LC-264-丑数2"><a name="p264"></a> <a href="https://leetcode-cn.com/problems/ugly-number-ii/" target="_blank" rel="noopener">LC-264 丑数2</a></h3><p>给你一个整数 n ，请你找出并返回第 n 个 丑数 。<br>丑数 就是只包含质因数 2、3 和/或 5 的正整数。<br>示例 1：<br>输入：n = 10<br>输出：12<br>解释：[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。</p><p>方法一，利用堆排序，根据丑数定义，对前一个数以此乘以 [2, 3, 5]，不断加入到堆中。一头入堆，一头出堆，直到等于n结束。</p><pre><code class="language-python">class Solution:    def nthUglyNumber(self, n: int) -&gt; int:        hq = []        heapq.heappush(hq, 1)        used = set()        used.add(1)        ugly_factors = [2, 3, 5]        for i in range(n):            print(i)            x = heapq.heappop(hq)            if i == n-1:                return x            for u in ugly_factors:                if x * u not in used:                    used.add(x * u)                    heapq.heappush(hq, x * u)        return -1</code></pre><p>方法二，动态规划三指针，三个指针起始指向 index = 0，更新时将3个指针分别乘以对应的 2，3，5；最小的值更新为下一个index，被选中的指针+1</p><p><strong>转移方程：dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)</strong></p><pre><code class="language-python">class Solution:    def nthUglyNumber(self, n: int) -&gt; int:        dp = [1] * n        p2, p3, p5 = 0, 0, 0        for i in range(1, n, 1):            p2_num, p3_num, p5_num = dp[p2] * 2, dp[p3] * 3, dp[p5] * 5            min_num = min(p2_num, p3_num, p5_num)            dp[i] = min_num            if min_num == p2_num: p2 += 1            if min_num == p3_num: p3 += 1            if min_num == p5_num: p5 += 1        return dp[-1]</code></pre><h3 id="a-name-p279-a-LC-279-完全平方数"><a name="p279"></a> <a href="https://leetcode-cn.com/problems/perfect-squares/" target="_blank" rel="noopener">LC-279 完全平方数</a></h3><p>给定正整数 n，找到若干个完全平方数（比如 1, 4, 9, 16, …）使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。</p><p>给你一个整数 n ，返回和为 n 的完全平方数的 最少数量 。<br>示例 1：<br>输入：n = 12<br>输出：3<br>解释：12 = 4 + 4 + 4</p><p>示例 2：<br>输入：n = 13<br>输出：2<br>解释：13 = 4 + 9</p><p><strong>转移方程: dp[num] = min(dp[num], dp[num - i * i] + 1)</strong></p><pre><code class="language-python">class Solution:    def numSquares(self, n: int) -&gt; int:        dp = [1] * (n + 1)        dp[0] = 0        for num in range(1, n + 1):            dp[num] = num            tmp = []            for i in range(1, int(num ** 0.5) + 1):                tmp.append(dp[num - i**2] + 1)            dp[num] = min(tmp)        return dp[-1]</code></pre><h3 id="a-name-p309-a-LC-309-最佳买卖股票时机含冷冻期"><a name="p309"></a> <a href="https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/" target="_blank" rel="noopener">LC-309 最佳买卖股票时机含冷冻期</a></h3><p>给定一个整数数组，其中第 i 个元素代表了第 i 天的股票价格 。​<br>设计一个算法计算出最大利润。在满足以下约束条件下，你可以尽可能地完成更多的交易（多次买卖一支股票）:<br>你不能同时参与多笔交易（你必须在再次购买前出售掉之前的股票）。<br>卖出股票后，你无法在第二天买入股票 (即冷冻期为 1 天)。<br>示例:<br>输入: [1,2,3,0,2]<br>输出: 3<br>解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]</p><p>股票类题目运用状态机，设定3个维度：1. 天数、2. 次数、3. 状态（持有、不持有、不持有冷冻），此题没有次数，只需要构建2维数组</p><p><strong>转移方程：</strong></p><pre><code class="language-python"># 持有dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])# 不持有，不冷冻dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])# 不持有，冷冻dp[i][2] = max(dp[i-1][1], dp[i-1][2])</code></pre><pre><code class="language-python">class Solution:    def maxProfit(self, prices: List[int]) -&gt; int:        if len(prices) &lt;= 1:            return 0                # 天数、次数、状态：持有 不持有 冷冻        dp = [[0 for j in range(3)] for _ in range(len(prices))]        dp[0][0] = -prices[0]        for i in range(1, len(prices)):            for j in range(3):                # 持有                dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])                # 不持有 不冷冻                dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])                # 不持有 冷冻                dp[i][2] = max(dp[i-1][1], dp[i-1][2])        return max(dp[-1][1], dp[-1][2])</code></pre><h3 id="a-name-p188-a-LC-188-买卖股票的最佳时机-IV"><a name="p188"></a> <a href="https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/" target="_blank" rel="noopener">LC-188 买卖股票的最佳时机 IV</a></h3><p>给定一个整数数组 prices ，它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。注意：你不能同时参与多笔交易（你必须在再次购买前出售掉之前的股票）。</p><p>示例 1：<br>输入：k = 2, prices = [2,4,1]<br>输出：2<br>解释：在第 1 天 (股票价格 = 2) 的时候买入，在第 2 天 (股票价格 = 4) 的时候卖出，这笔交易所能获得利润 = 4 - 2 = 2。</p><p>重点：</p><ul><li>第 1 天就买入需要进行初始化 - prices[0]</li><li>交易成功 1 次需要更新 k</li></ul><p><strong>转移方程：</strong><br><strong>dp[i][0][k]=max(dp[i−1][0][k],dp[i−1][1][k]+prices[i])</strong><br><strong>dp[i][1][k]=max(dp[i−1][1][k],dp[i−1][0][k−1]−prices[i])</strong></p><pre><code class="language-python">class Solution:    def maxProfit(self, k: int, prices: List[int]) -&gt; int:        if len(prices) &lt;= 1 or k &lt;= 0:            return 0                dp = [[[i for i in range(2)] for j in range(k+1)] for n in range(len(prices))]        # 补第一天就买入的case, k=0，交易不会成功        for i in range(1, k+1):            dp[0][i][1] = -prices[0]        for i in range(1, len(prices)):            for j in range(1, k+1):                dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])                dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])        return max([j[0] for i in dp for j in i])</code></pre><h3 id="a-name-p343-a-LC-343-整数拆分"><a name="p343"></a> <a href="https://leetcode-cn.com/problems/integer-break/" target="_blank" rel="noopener">LC-343 整数拆分</a></h3><p>给定一个正整数 n，将其拆分为至少两个正整数的和，并使这些整数的乘积最大化。 返回你可以获得的最大乘积。<br>示例 1:<br>输入: 2<br>输出: 1<br>解释: 2 = 1 + 1, 1 × 1 = 1。</p><p>示例 2:<br>输入: 10<br>输出: 36<br>解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。</p><p><strong>转移函数：</strong></p><pre><code class="language-python">class Solution:    def integerBreak(self, n: int) -&gt; int:        dp = [0] * (n + 1)        dp[0] = 1        dp[1] = 1        for i in range(2, n + 1):            for j in range(i):                dp[i] = max(dp[i], j * (i-j), j * dp[i-j])        return dp[-1]</code></pre><h3 id="a-name-p416-a-LC-416-分割等和子集"><a name="p416"></a> <a href="https://leetcode-cn.com/problems/partition-equal-subset-sum/" target="_blank" rel="noopener">LC-416 分割等和子集</a></h3><p>给你一个 只包含正整数 的 非空 数组 nums。<br>请你判断是否可以将这个数组分割成两个子集，使得两个子集的元素和相等。<br>示例 1：<br>输入：nums = [1,5,11,5]<br>输出：true<br>解释：数组可以分割成 [1, 5, 5] 和 [11] 。</p><p>动态规划，01 背包问题</p><p><strong>转移方程：</strong><br><strong>dp[i][j] = dp[i-1][j] or dp[i-1][j - nums[i]]</strong></p><pre><code class="language-python">class Solution:    def canPartition(self, nums: List[int]) -&gt; bool:        sum_num = sum(nums)        if sum_num % 2 == 1:            return False        target = sum_num // 2        dp = [[False] * (target + 1) for i in range(len(nums))]        for i in range(len(nums)):            dp[i][0] = True        for i in range(len(nums)):            for j in range(1, target + 1):                if nums[i] == j:                    dp[i][j] = True                if nums[i] &lt; j:                    dp[i][j] = dp[i-1][j] or dp[i-1][j - nums[i]]                # 比目标值大，直接拿上一位                if nums[i] &gt; j:                    dp[i][j] = dp[i-1][j]        # print(dp)        return dp[-1][-1]</code></pre><h3 id="a-name-p42-a-LC-42-接雨水"><a name="p42"></a> <a href="https://leetcode-cn.com/problems/trapping-rain-water/" target="_blank" rel="noopener">LC-42 接雨水</a></h3><p>给定 n 个非负整数表示每个宽度为 1 的柱子的高度图，计算按此排列的柱子，下雨之后能接多少雨水。<br>输入：height = [0,1,0,2,1,0,1,3,2,1,2,1]<br>输出：6<br>解释：上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图，在这种情况下，可以接 6 个单位的雨水（蓝色部分表示雨水）。</p><p><strong>关键方程：res += min(l_max[i], r_max[i]) - height[i]</strong></p><pre><code class="language-python">class Solution:    def trap(self, height: List[int]):        # 核心思想： res += min(l_max[i], r_max[i]) - height[i];        if not height:            return 0                left_p = 0        right_p = len(height) - 1        left_max = 0        right_max = 0        res = 0        while left_p &lt;= right_p:            left_max = max(left_max, height[left_p])            right_max = max(right_max, height[right_p])            if left_max &lt; right_max:                res += left_max - height[left_p]                left_p += 1            else:                res += right_max - height[right_p]                right_p -= 1        return res</code></pre><p>也可以用dp来解，<strong>转移方程left_max[i] = max(height[i], left_max[i-1])</strong><br><strong>right_max[i] = max(height[i], right_max[i + 1])</strong></p><pre><code class="language-python">class Solution:    def trap(self, height: List[int]) -&gt; int:        # 核心思想： res += min(l_max[i], r_max[i]) - height[i];        if not height:            return 0               left_max = [height[0]] + [0] * (len(height) - 1)        right_max = [0] * (len(height) - 1) + [height[-1]]        for i in range(1, len(height)):            left_max[i] = max(height[i], left_max[i-1])        for i in range(len(height) - 2, -1 , -1):            right_max[i] = max(height[i], right_max[i + 1])                res = 0        for j in range(len(height)):            ans = min(left_max[j], right_max[j]) - height[j]            res += ans        return res</code></pre><h3 id="a-name-p32-a-LC-32-最长有效括号"><a name="p32"></a> <a href="https://leetcode-cn.com/problems/longest-valid-parentheses/" target="_blank" rel="noopener">LC-32 最长有效括号</a></h3><p>给你一个只包含 ‘(’ 和 ‘)’ 的字符串，找出最长有效（格式正确且连续）括号子串的长度。<br>输入：s = “)()())”<br>输出：4<br>解释：最长有效括号子串是 “()()”</p><p>用堆来保存括号的index</p><pre><code class="language-python">class Solution:    def longestValidParentheses(self, s: str) -&gt; int:        stack = [-1]        res = 0        for i, j in enumerate(s):            if j == &quot;(&quot;:                stack.append(i)            else:                stack.pop()                if stack:                    # stack 不为空正常计算max                    res = max(res, i - stack[-1])                else:                    # stac 为空则直接把右括号index压入                    stack.append(i)        return result</code></pre><h3 id="a-name-p72-a-LC-72-编辑距离"><a name="p72"></a> <a href="https://leetcode-cn.com/problems/edit-distance/" target="_blank" rel="noopener">LC-72 编辑距离</a></h3><p>给你两个单词 word1 和 word2，请你计算出将 word1 转换成 word2 所使用的最少操作数 。<br>你可以对一个单词进行如下三种操作：<br>插入一个字符<br>删除一个字符<br>替换一个字符</p><p>示例1：<br>输入：word1 = “horse”, word2 = “ros”<br>输出：3<br>解释：<br>horse -&gt; rorse (将 ‘h’ 替换为 ‘r’)<br>rorse -&gt; rose (删除 ‘r’)<br>rose -&gt; ros (删除 ‘e’)</p><p><strong>转移方程：if word1[i] == word2[j] then dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1])</strong><br><strong>else dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)</strong></p><p>初始化第一行和第一列，想到二维矩阵，dp[i][j]由dp[i-1][j-1]表示替换操作，dp[i][j]由dp[i-1][j]表示删除操作+1，dp[i][j]由dp[i][j-1]表示插入操作+1<br>dp[i][0] 和 dp[0][j] 初始化为对应单词和空字符串之间的编辑距离。</p><pre><code class="language-python">class Solution:    def minDistance(self, word1: str, word2: str) -&gt; int:        if len(word1) == 0:            return len(word2)        if len(word2) == 0:            return len(word1)                dp = [[0] * (len(word1) + 1) for _ in range(len(word2) + 1)]        for i in range(1, len(dp)):            dp[i][0] = i        for j in range(1, len(dp[0])):            dp[0][j] = j        for i in range(1, len(dp)):            for j in range(1, len(dp[0])):                if word2[i-1] == word1[j-1]:                    dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1])                else:                    dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)        return dp[-1][-1]</code></pre><h1>树</h1><h3 id="建树">建树</h3><p>根据给定的list按照层次遍历建立树</p><pre><code class="language-python">class TreeNode:    def __init__(self, val=0, left=None, right=None):        self.val = val        self.left = left        self.right = rightfrom collections import dequedef build_tree_from_list(values):    if not values:        return None    root = TreeNode(values[0])    queue = deque([root])    index = 1    while queue and index &lt; values.length:        node = queue.popleft()        if index &lt; len(values) and values[index] is not None:            node.left = TreeNode(values[index])            queue.append(node.left)        index += 1        if index &lt; len(values) and values[index] is not None:            node.right = TreeNode(values[index])            queue.append(node.right)        index += 1    return root# 给定的列表values = [5, 4, 8, 11, None, 13, 4, 7, 2, None, None, 5, 1]# 构建二叉树root = build_tree_from_list(values)# 辅助函数：层序遍历以验证树结构def level_order_traversal(root):    if not root:        return    queue = deque([root])    while queue:        node = queue.popleft()        if node:            print(node.val, end=' ')            queue.append(node.left)            queue.append(node.right)        else:            print(&quot;None&quot;, end=' ')print(&quot;Level order traversal of the tree:&quot;)level_order_traversal(root)</code></pre><h3 id="a-name-p103-a-LC-103-二叉树锯齿形遍历"><a name="p103"></a> <a href="https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/" target="_blank" rel="noopener">LC-103 二叉树锯齿形遍历</a></h3><p>给定一个二叉树，返回其节点值的锯齿形层序遍历。（即先从左往右，再从右往左进行下一层遍历，以此类推，层与层之间交替进行）。</p><p>例如：<br>给定二叉树 [3,9,20,null,null,15,7],<br>3<br>/ \<br>9  20<br>/  \<br>15   7</p><p>返回锯齿形层序遍历如下：<br>[<br>[3],<br>[20,9],<br>[15,7]<br>]</p><p>BFS和DFS，BFS一定使用队列，本题由于有奇偶行的区别，考虑使用双端队列；DFS递归参数要代上层号。</p><pre><code class="language-python"># BFSclass Solution:    def zigzagLevelOrder(self, root: TreeNode) -&gt; List[List[int]]:        if not root:            return []        queue = deque()        queue.append(root)        flag = 1        res = []        while queue:            level = deque() # 每层node存放            for i in range(len(queue)):                item = queue.popleft()                 # 弹出后 做2件事：                # 1. 存入 level queue                if flag % 2 == 1:                    level.append(item.val)                else:                    level.appendleft(item.val)                # 2. 左右子节点存入 queue                if item.left:                    queue.append(item.left)                if item.right:                    queue.append(item.right)            res.append(level)            flag += 1        return res</code></pre><pre><code class="language-python"># DFSclass Solution:    def zigzagLevelOrder(self, root: TreeNode) -&gt; List[List[int]]:        res = []        def recursive(root, index):            # 递归要把完成的程序栈 return            if not root:                return            if index &gt; len(res):                res.append(deque())            if index % 2 == 1:                res[index-1].append(root.val)            else:                res[index-1].appendleft(root.val)            recursive(root.left, index + 1)            recursive(root.right, index + 1)        recursive(root, 1)        return [list(item) for item in res]</code></pre><h3 id="a-name-p102-a-LC-102-二叉树的层序遍历"><a name="p102"></a> <a href="https://leetcode-cn.com/problems/binary-tree-level-order-traversal/" target="_blank" rel="noopener">LC-102 二叉树的层序遍历</a></h3><p>给你一个二叉树，请你返回其按 层序遍历 得到的节点值。 （即逐层地，从左到右访问所有节点）。</p><p>示例：<br>二叉树：[3,9,20,null,null,15,7],<br>3<br>/ \<br>9  20<br>/  \<br>15   7</p><p>返回其层序遍历结果：</p><p>[<br>[3],<br>[9,20],<br>[15,7]<br>]</p><p>层序遍历BFS一般步骤：</p><ol><li>边界判断</li><li>新建deque，加入root</li><li>queue循环当前queue中的长度，并新建每一层的结果存储集level</li><li>2件事，加入level结果，加入queue以便做下一次遍历</li></ol><pre><code class="language-python"># BFSclass Solution:    def levelOrder(self, root: TreeNode) -&gt; List[List[int]]:        if not root:            return []        queue = deque()        queue.append(root)        res = []        while queue:            level = []            for i in range(len(queue)):                node = queue.popleft()                level.append(node.val)                if node.left:                    queue.append(node.left)                if node.right:                    queue.append(node.right)            res.append(level)        return res</code></pre><p>深度遍历DFS一般步骤：</p><ol><li>判断边界</li><li>写遍历函数体，递归要return函数栈</li><li>对每一个root节点，做2件事：加入到res对应的层中，前序遍历以此递归</li></ol><pre><code class="language-python"># DFSclass Solution:    def levelOrder(self, root: TreeNode) -&gt; List[List[int]]:        if not root:            return []        res = []        def recursive(root, index):            if not root:                return             if index &gt; len(res):                res.append([])            res[index - 1].append(root.val)            recursive(root.left, index + 1)            recursive(root.right, index + 1)        recursive(root, 1)        return res</code></pre><h3 id="a-name-p107-a-LC-107-二叉树的层序遍历2"><a name="p107"></a> <a href="https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/" target="_blank" rel="noopener">LC-107 二叉树的层序遍历2</a></h3><p>给定一个二叉树，返回其节点值自底向上的层序遍历。 （即按从叶子节点所在层到根节点所在的层，逐层从左向右遍历）</p><p>例如：<br>给定二叉树 [3,9,20,null,null,15,7],<br>3<br>/ \<br>9  20<br>/  \<br>15   7<br>返回其自底向上的层序遍历为：</p><p>[<br>[15,7],<br>[9,20],<br>[3]<br>]</p><p>和102唯一的区别就是存放最终结果的时候是倒序，自底向上累计。</p><pre><code class="language-python">class Solution:    def levelOrderBottom(self, root: TreeNode) -&gt; List[List[int]]:        if not root:            return []        res = []        def recursive(root, index):            if not root:                return             if index &gt; len(res):                res.insert(0, [])            res[len(res)-index].append(root.val) # 注意append的index是最大行数-当前index=0            recursive(root.left, index + 1)            recursive(root.right, index + 1)        recursive(root, 1)        return res</code></pre><h3 id="a-name-p236-a-LC-236-二叉树的最近公共祖先"><a name="p236"></a> <a href="https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree" target="_blank" rel="noopener">LC-236 二叉树的最近公共祖先</a></h3><p>输入：root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1<br>输出：3<br>解释：节点 5 和节点 1 的最近公共祖先是节点 3 。</p><pre><code class="language-python">class Solution:    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -&gt; 'TreeNode':        # 后序遍历        if root == p or root == q or root == None:            return root        left = self.lowestCommonAncestor(root.left, p, q)        right = self.lowestCommonAncestor(root.right, p, q)        # 得到该节点对应的左右节点后，要决定根据状态返回 root、left、right 哪个节点        if left and right:            return root        if left == None and right:            return right        elif right == None and left:            return left        else:            return None</code></pre><h3 id="a-name-p98-a-LC-98-验证二叉搜索树"><a name="p98"></a> <a href="https://leetcode-cn.com/problems/validate-binary-search-tree/" target="_blank" rel="noopener">LC-98 验证二叉搜索树</a></h3><p>给定一个二叉树，判断其是否是一个有效的二叉搜索树。<br>假设一个二叉搜索树具有如下特征：<br>节点的左子树只包含小于当前节点的数。<br>节点的右子树只包含大于当前节点的数。<br>所有左子树和右子树自身必须也是二叉搜索树。</p><p>解释: 输入为: [5,1,4,null,null,3,6]。<br>根节点的值为 5 ，但是其右子节点值为 4 。</p><p>中序遍历，BST重要特征是中序遍历后的数组是按顺序的</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def isValidBST(self, root: TreeNode) -&gt; bool:        if not root: return True        self.pre = None        def midorder(root):             left , right= True , True            if root.left: left = midorder(root.left)            if self.pre and self.pre.val &gt;= root.val :#前一个节点的值是否小于当前节点的值                return False            self.pre = root#记录前一个节点            if root.right: right = midorder(root.right)            return left and right        return midorder(root)</code></pre><h3 id="a-name-p199-a-LC-199-二叉树右视图"><a name="p199"></a> <a href="https://leetcode-cn.com/problems/binary-tree-right-side-view/" target="_blank" rel="noopener">LC-199 二叉树右视图</a></h3><p>给定一个二叉树的 根节点 root，想象自己站在它的右侧，按照从顶部到底部的顺序，返回从右侧所能看到的节点值。<br>输入: [1,2,3,null,5,null,4]<br>输出: [1,3,4]</p><p>BFS，层序遍历，遍历到最右边，加入到结果集中。</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def rightSideView(self, root: TreeNode) -&gt; List[int]:        if not root:            return []        queue = deque()        queue.append(root)        res = []        while queue:            n = len(queue)            for i in range(len(queue)):                node = queue.popleft()                if node.left:                    queue.append(node.left)                if node.right:                    queue.append(node.right)                if i == (n - 1):                    res.append(node.val)        return res</code></pre><p>DFS, 前序遍历，右节点先行</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def rightSideView(self, root: TreeNode) -&gt; List[int]:        res = []        def dfs(root, deep):            if not root:                return             if deep == len(res):                res.append(root.val)            deep += 1            dfs(root.right, deep)            dfs(root.left, deep)        dfs(root, 0)        return res</code></pre><h3 id="a-name-p101-a-LC-101-对称二叉树"><a name="p101"></a> <a href="https://leetcode-cn.com/problems/symmetric-tree/" target="_blank" rel="noopener">LC-101 对称二叉树</a></h3><p>给定一个二叉树，检查它是否是镜像对称的。</p><p>例如，二叉树 [1,2,2,3,4,4,3] 是对称的。<br>1<br>/ \<br>2   2<br>/ \ / \<br>3  4 4  3</p><p>DFS，构造递归函数，入参是左右2个的节点。</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def isSymmetric(self, root: TreeNode) -&gt; bool:        if not root:            return True                def helper(left, right):            if not left and not right:                return True            if not left or not right or left.val != right.val:                return False                        ans = helper(left.left, right.right) and helper(left.right, right.left)            return ans        return helper(root.left, root.right)</code></pre><h3 id="a-name-p105-a-LC-105-从前序与中序遍历序列构造二叉树"><a name="p105"></a> <a href="https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/" target="_blank" rel="noopener">LC-105 从前序与中序遍历序列构造二叉树</a></h3><p>给定一棵树的前序遍历 preorder 与中序遍历  inorder。请构造二叉树并返回其根节点。<br>Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]<br>Output: [3,9,20,null,null,15,7]</p><ol><li>前序遍历确定根节点</li><li>中序遍历中拿到根节点index，分为左右子树</li><li>递归作为前序和中序的输入</li><li>代码整体是前序遍历<br>将大问题拆解成子问题</li></ol><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def buildTree(self, preorder: List[int], inorder: List[int]) -&gt; TreeNode:        if len(inorder) == 0:            return None                # 跟节点        root = TreeNode(preorder[0])        # 中间节点        mid = inorder.index(preorder[0])        # 左子树        root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])        # 右子树        root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])        return root</code></pre><h3 id="a-name-p112-a-LC-112-路径总和"><a name="p112"></a> <a href="https://leetcode-cn.com/problems/path-sum//" target="_blank" rel="noopener">LC-112 路径总和</a></h3><p>给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ，判断该树中是否存在 根节点到叶子节点 的路径，这条路径上所有节点值相加等于目标和 targetSum 。<br>叶子节点 是指没有子节点的节点。</p><p>输入：root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22<br>输出：true</p><p>输入：root = [1,2], targetSum = 0<br>输出：false</p><p>DFS 递归</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -&gt; bool:        if not root:            return False                def dfs(root, target):            if not root:                return False            if not root.left and not root.right:                if root.val == target:                    return True                else:                    return False            l = dfs(root.left, target - root.val)            r = dfs(root.right, target - root.val)            return l or r        return dfs(root, targetSum)</code></pre><h3 id="a-name-p113-a-LC-113-路径总和-II"><a name="p113"></a> <a href="https://leetcode-cn.com/problems/path-sum-ii/" target="_blank" rel="noopener">LC-113 路径总和 II</a></h3><p>给你二叉树的根节点 root 和一个整数目标和 targetSum ，找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。<br>叶子节点 是指没有子节点的节点。</p><p>输入：root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22<br>输出：[[5,4,11,2],[5,8,4,5]]</p><p>DFS，不需要回溯，没有需要维护的可变状态，不断递归就可</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def pathSum(self, root: Optional[TreeNode], targetSum: int) -&gt; List[List[int]]:        if not root:            return []                def dfs(root, target, path):            if not root:                return            if not root.left and not root.right:                if target - root.val == 0:                    path.append(root.val)                    res.append(path)            dfs(root.left, target - root.val, path + [root.val])            dfs(root.right, target - root.val, path + [root.val])        res = []        dfs(root, targetSum, [])        return res</code></pre><h3 id="a-name-p124-a-LC-124-二叉树中的最大路径和"><a name="p124"></a> <a href="https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/" target="_blank" rel="noopener">LC-124 二叉树中的最大路径和</a></h3><p>路径 被定义为一条从树中任意节点出发，沿父节点-子节点连接，达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点，且不一定经过根节点。<br>路径和 是路径中各节点值的总和。<br>给你一个二叉树的根节点 root ，返回其 最大路径和 。</p><p>输入：root = [1,2,3]<br>输出：6<br>解释：最优路径是 2 -&gt; 1 -&gt; 3 ，路径和为 2 + 1 + 3 = 6</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def maxPathSum(self, root: TreeNode) -&gt; int:        self.res = float('-inf')        def helper(root):            if not root:                return 0                        left = helper(root.left)            right = helper(root.right)            # inner 内部路径            self.res = max(self.res, root.val + left + right)            # outside 需要返回给父节点的外部路径            inner_max = max(left, right) + root.val            # inner_max &lt;=0 时返回 0            if inner_max &lt;= 0:                return 0            else:                return inner_max        helper(root)        return self.res</code></pre><h3 id="a-name-p129-a-LC-129-求根节点到叶节点数字之和"><a name="p129"></a> <a href="https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/" target="_blank" rel="noopener">LC-129 求根节点到叶节点数字之和</a></h3><p>给你一个二叉树的根节点 root ，树中每个节点都存放有一个 0 到 9 之间的数字。<br>每条从根节点到叶节点的路径都代表一个数字：<br>例如，从根节点到叶节点的路径 1 -&gt; 2 -&gt; 3 表示数字 123 。<br>计算从根节点到叶节点生成的 所有数字之和.叶节点 是指没有子节点的节点。</p><p>输入：root = [4,9,0,5,1]<br>输出：1026<br>解释：<br>从根到叶子节点路径 4-&gt;9-&gt;5 代表数字 495<br>从根到叶子节点路径 4-&gt;9-&gt;1 代表数字 491<br>从根到叶子节点路径 4-&gt;0 代表数字 40<br>因此，数字总和 = 495 + 491 + 40 = 1026</p><p>前序遍历，先处理root.val * 10</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def sumNumbers(self, root: TreeNode) -&gt; int:        res = 0        def helper(root, tmp):            if not root:                return 0            # 前序遍历            tmp = tmp * 10 + root.val;            if not root.left and not root.right:                return tmp            return helper(root.left, tmp) + helper(root.right, tmp)        return helper(root, 0)</code></pre><h3 id="a-name-p958-a-LC-958-二叉树的完全性检验"><a name="p958"></a> <a href="https://leetcode-cn.com/problems/check-completeness-of-a-binary-tree/" target="_blank" rel="noopener">LC-958 二叉树的完全性检验</a></h3><p>给定一个二叉树，确定它是否是一个完全二叉树。</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-1.png" alt=""><br>输入：[1,2,3,4,5,6]<br>输出：true<br>解释：最后一层前的每一层都是满的（即，结点值为 {1} 和 {2,3} 的两层），且最后一层中的所有结点（{4,5,6}）都尽可能地向左。<br><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-2.png" alt=""><br>输入：[1,2,3,4,5,null,7]<br>输出：false<br>解释：值为 7 的结点没有尽可能靠向左侧。</p><pre><code class="language-python"># class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def isCompleteTree(self, root: TreeNode) -&gt; bool:        if not root:            return False        queue = deque()        queue.append(root)        while queue:            for i in range(len(queue)):                item = queue.popleft()                if not item:                    print(set(queue))                    if set(queue) == {None}:                        return True                    else:                        return False                queue.append(item.left)                queue.append(item.right)</code></pre><h3 id="a-name-p104-a-LC-104-二叉树的最大深度"><a name="p104"></a> <a href="https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/" target="_blank" rel="noopener">LC-104 二叉树的最大深度</a></h3><p>给定一个二叉树，找出其最大深度。<br>二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。<br>说明: 叶子节点是指没有子节点的节点。<br>示例：<br>给定二叉树 [3,9,20,null,null,15,7]，</p><p>后续遍历</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def maxDepth(self, root: TreeNode) -&gt; int:        def helper(root, deep):            if not root:                return 0            l = helper(root.left, deep)            r = helper(root.right, deep)            return 1 + max(l, r)        return helper(root, 0)</code></pre><h3 id="a-name-p226-a-LC-226-反转二叉树"><a name="p226"></a> <a href="https://leetcode-cn.com/problems/invert-binary-tree/" target="_blank" rel="noopener">LC-226 反转二叉树</a></h3><pre><code class="language-python">class Solution:    def invertTree(self, root: TreeNode) -&gt; TreeNode:        if not root:            return         tmp = root.left        root.left = root.right        root.right = tmp        self.invertTree(root.left)         self.invertTree(root.right)        return root</code></pre><h3 id="a-name-p114-a-LC-114-二叉树展开成链表"><a name="p114"></a> <a href="https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list" target="_blank" rel="noopener">LC-114 二叉树展开成链表</a></h3><p>给你二叉树的根结点 root ，请你将它展开为一个单链表：<br>展开后的单链表应该同样使用 TreeNode ，其中 right 子指针指向链表中下一个结点，而左子指针始终为 null 。<br>展开后的单链表应该与二叉树 先序遍历 顺序相同。</p><p>输入：root = [1,2,5,3,4,null,6]<br>输出：[1,null,2,null,3,null,4,null,5,null,6]</p><ul><li>后序遍历，递归</li><li>左节点换到右节点</li><li>右节点接到左节点右边</li></ul><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def flatten(self, root: TreeNode) -&gt; None:        &quot;&quot;&quot;        Do not return anything, modify root in-place instead.        &quot;&quot;&quot;        def dfs(root):            if not root:                return None            # 后序遍历            dfs(root.left)            dfs(root.right)            # 拿到左右节点            l = root.left            r = root.right            # 对于root来说左节点置空，原左节点置右            root.left = None            root.right = l            p = root            # 遍历当前右节点到最后，右节点街上            while p.right:                p = p.right            p.right = r        dfs(root)</code></pre><h3 id="a-name-p230-a-LC-230-二叉树搜索树中的第k个小的元素"><a name="p230"></a> <a href="https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/" target="_blank" rel="noopener">LC-230 二叉树搜索树中的第k个小的元素</a></h3><p>给定一个二叉搜索树的根节点 root ，和一个整数 k ，请你设计一个算法查找其中第 k 个最小元素（从 1 开始计数）。<br>输入：root = [5,3,6,2,4,null,null,1], k = 3<br>输出：3</p><p>BST 搜索树，中序遍历可以得到排序后的序列</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def kthSmallest(self, root: TreeNode, k: int) -&gt; int:        res = []        def inorder(root):            if not root:                return []            inorder(root.left)            res.append(root.val)            inorder(root.right)        inorder(root)        return res[k-1]</code></pre><h3 id="a-name-p662-a-LC-662-二叉树最大宽度"><a name="p662"></a> <a href="https://leetcode-cn.com/problems/maximum-width-of-binary-tree/" target="_blank" rel="noopener">LC-662 二叉树最大宽度</a></h3><p>给定一个二叉树，编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树（full binary tree）结构相同，但一些节点为空。<br>每一层的宽度被定义为两个端点（该层最左和最右的非空节点，两端点间的null节点也计入长度）之间的长度。</p><p>输入:<br>1<br>/ <br>3   2<br>/     \<br>5       9<br>/         <br>6           7<br>输出: 8<br>解释: 最大值出现在树的第 4 层，宽度为 8 (6,null,null,null,null,null,null,7)。</p><p>层次遍历</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution(object):    def widthOfBinaryTree(self, root):        &quot;&quot;&quot;        :type root: TreeNode        :rtype: int        &quot;&quot;&quot;        if not root:            return 0        queue = []        queue.append([root, 1])        res = 1        while queue:            # 一层总数            for i in range(len(queue)):                tmp = queue.pop(0)                node = tmp[0]                index = tmp[1]                # calculate base on last queue                if node.left:                    queue.append([node.left, index * 2])                if node.right:                    queue.append([node.right, index * 2 + 1 ])                if queue:                    res = max(res, queue[-1][1] - queue[0][1] + 1)        return res</code></pre><h3 id="a-name-jz26-a-JZ-26-树的子结构"><a name="jz26"></a> <a href="https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/" target="_blank" rel="noopener">JZ-26 树的子结构</a></h3><p>输入两棵二叉树A和B，判断B是不是A的子结构。(约定空树不是任意一个树的子结构)</p><p>B是A的子结构， 即 A中有出现和B相同的结构和节点值。</p><p>例如:<br>给定的树 A:</p><p>3<br>    / <br>   4   5<br>  / <br> 1   2<br>给定的树 B：</p><p>4 <br>  /<br> 1<br>返回 true，因为 B 与 A 的一个子树拥有相同的结构和节点值。</p><p>示例 1：</p><p>输入：A = [1,2,3], B = [3,1]<br>输出：false</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, x):#         self.val = x#         self.left = None#         self.right = Noneclass Solution:    def isSubStructure(self, A: TreeNode, B: TreeNode) -&gt; bool:        if not B:            return False        def helper(nodeA, nodeB):            # b没有节点了一定true            if not nodeB:                return True            # a没有节点了一定false            if not nodeA:                return False            # a 和 b 的节点不一样一定false            if nodeA.val != nodeB.val:                return False            return helper(nodeA.left, nodeB.left) and helper(nodeA.right, nodeB.right)                    def dfs(nodeA):            if not nodeA:                return False            # 找到相同的开头            if nodeA.val == B.val:                if helper(nodeA, B):                    return True            return dfs(nodeA.left) or dfs(nodeA.right)        return dfs(A)</code></pre><h1>链表</h1><h3 id="a-name-p206-a-LC-206-反转链表"><a name="p206"></a> <a href="https://leetcode-cn.com/problems/reverse-linked-list/" target="_blank" rel="noopener">LC-206 反转链表</a></h3><p>给你单链表的头节点 head ，请你反转链表，并返回反转后的链表。</p><p>输入：head = [1,2,3,4,5]<br>输出：[5,4,3,2,1]</p><pre><code class="language-python">class Solution:    def reverseList(self, head: ListNode) -&gt; ListNode:        if not head or not head.next:            return head        # 后续节点        last = self.reverseList(head.next)        # 1. 前序节点反转        head.next.next = head        # 2. 砍掉本身的next指针        head.next = None        return last</code></pre><pre><code class="language-python">class Solution:    def reverseList(self, head: ListNode) -&gt; ListNode:        pre, cur = None, head        while cur:            nxt = cur.next            # 接到pre后面            cur.next = pre            pre = cur            cur = nxt        return pre</code></pre><h3 id="a-name-p25-a-LC-25-K个一组反转链表"><a name="p25"></a> <a href="https://https://leetcode-cn.com/problems/reverse-nodes-in-k-group/" target="_blank" rel="noopener">LC-25 K个一组反转链表</a></h3><p>给你一个链表，每 k 个节点一组进行翻转，请你返回翻转后的链表。<br>k 是一个正整数，它的值小于或等于链表的长度。<br>如果节点总数不是 k 的整数倍，那么请将最后剩余的节点保持原有顺序。</p><p>输入：head = [1,2,3,4,5], k = 3<br>输出：[3,2,1,4,5]</p><pre><code class="language-python">class Solution:    def reverseKGroup(self, head: ListNode, k: int) -&gt; ListNode:        def reverse(left, right):            pre, cur = left, left.next            # pre 收集结果            first, last = pre, cur            while cur != right:                nxt = cur.next                cur.next = pre                pre = cur                cur = nxt                            first.next = pre            last.next = right            return last        dummy = ListNode(-1)        dummy.next = head        left, right = dummy, head        cnt = 0        while right:            cnt += 1            right = right.next            if cnt % k == 0:                left = reverse(left, right)        return dummy.next</code></pre><p>运用翻转链表的子模块，在主模块里的first和last，first 始终表示进行翻转组的前一个，last 表示最终的最后一个节点，其实也就是起始翻转的第一个节点。<br>K个一组翻转链表要点：</p><ul><li>构建虚拟头结点，涉及几个一组、快慢、双指针问题，都要涉及到2个指正前进的问题</li><li>翻转部分子函数，传入的应该是翻转组的前一个和后一个节点，这样可以串联最后的结果</li><li>子模型是翻转链表，pre实则是收集最终结果，last是该子模块的最后一个节点，也是下一个迭代的起点</li></ul><h3 id="a-name-p92-a-LC-92-反转链表2"><a name="p92"></a> <a href="https://leetcode-cn.com/problems/reverse-linked-list-ii/" target="_blank" rel="noopener">LC-92 反转链表2</a></h3><p>给你单链表的头指针 head 和两个整数 left 和 right ，其中 left &lt;= right 。请你反转从位置 left 到位置 right 的链表节点，返回 反转后的链表 。</p><p>输入：head = [1,2,3,4,5], left = 2, right = 4<br>输出：[1,4,3,2,5]</p><p>链表头插法解题</p><pre><code class="language-python">class Solution:    def reverseBetween(self, head: ListNode, left: int, right: int) -&gt; ListNode:        dummy_node = ListNode(-1)        dummy_node.next = head        pre = dummy_node        for i in range(left-1):            pre = pre.next        cur = pre.next        # 头插法        for i in range(right - left):            nxt = cur.next            cur.next = nxt.next            nxt.next = pre.next            pre.next = nxt        return dummy_node.next</code></pre><p>头插法要点：</p><ul><li>得到 pre, cur 和 nxt</li><li>cur next 指针指向 nxt 后一个</li><li>nxt next 指针指向 pre 的 next 即之前的头结点</li><li>pre next 指向 nxt</li></ul><h3 id="a-name-p21-a-LC-21-合并两个有序链表"><a name="p21"></a> <a href="https://leetcode-cn.com/problems/merge-two-sorted-lists/" target="_blank" rel="noopener">LC-21 合并两个有序链表</a></h3><p>将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。<br>输入：l1 = [1,2,4], l2 = [1,3,4]<br>输出：[1,1,2,3,4,4]</p><p>链表递归，实则就是回溯</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def mergeTwoLists(self, l1: ListNode, l2: ListNode):        if not l1: return l2        if not l2: return l1        if l1.val &lt; l2.val:            l1.next = self.mergeTwoLists(l1.next, l2)            # 回溯            return l1        else:            l2.next = self.mergeTwoLists(l2.next, l1)            # 回溯            return l2</code></pre><h3 id="a-name-p23-a-LC-23-合并k个有序链表"><a name="p23"></a> <a href="https://leetcode-cn.com/problems/merge-two-sorted-lists/" target="_blank" rel="noopener">LC-23 合并k个有序链表</a></h3><p>给你一个链表数组，每个链表都已经按升序排列。<br>请你将所有链表合并到一个升序链表中，返回合并后的链表。<br>示例 1：<br>输入：lists = [[1,4,5],[1,3,4],[2,6]]<br>输出：[1,1,2,3,4,4,5,6]<br>解释：链表数组如下：<br>[<br>1-&gt;4-&gt;5,<br>1-&gt;3-&gt;4,<br>2-&gt;6<br>]<br>将它们合并到一个有序链表中得到。1-&gt;1-&gt;2-&gt;3-&gt;4-&gt;4-&gt;5-&gt;6</p><p>方法一，逐个做合并2个有序链表。</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def mergeKLists(self, lists: List[ListNode]) -&gt; ListNode:        x = None        for i in lists:            x = self.mergeTwoLists(x, i)        return x    # 合并 2 个有序链表模板    def mergeTwoLists(self, l1: ListNode, l2: ListNode):        if not l1: return l2        if not l2: return l1        if l1.val &lt; l2.val:            l1.next = self.mergeTwoLists(l1.next, l2)            # 回溯            return l1        else:            l2.next = self.mergeTwoLists(l2.next, l1)            # 回溯            return l2</code></pre><p>方法二，归并排序</p><pre><code class="language-python">class Solution:    def mergeKLists(self, lists: List[ListNode]) -&gt; ListNode:        if not lists:return         n = len(lists)        return self.merge(lists, 0, n-1)    def merge(self,lists, left, right):        if left == right:            return lists[left]        mid = left + (right - left) // 2        # left, mid        l1 = self.merge(lists, left, mid)        # mid + 1, right        l2 = self.merge(lists, mid+1, right)        # 分拆到最后，再做 2 个链表合并        return self.mergeTwoLists(l1, l2)    def mergeTwoLists(self,l1, l2):        if not l1:return l2        if not l2:return l1        if l1.val &lt; l2.val:            l1.next = self.mergeTwoLists(l1.next, l2)            return l1        else:            l2.next = self.mergeTwoLists(l1, l2.next)            return l2</code></pre><h3 id="a-name-p141-a-LC-141-环形链表"><a name="p141"></a> <a href="https://leetcode-cn.com/problems/linked-list-cycle/" target="_blank" rel="noopener">LC-141 环形链表</a></h3><p>给定一个链表，判断链表中是否有环。</p><p>输入：head = [3,2,0,-4], pos = 1<br>输出：true<br>解释：链表中有一个环，其尾部连接到第二个节点。</p><p>简单题，快慢指针，相遇即有环</p><pre><code class="language-python">class Solution:    def hasCycle(self, head: ListNode) -&gt; bool:        dummy = ListNode(-1)        dummy.next = head        fast, slow = dummy.next, dummy        while fast != slow:            if not fast or not fast.next:                return False            fast = fast.next.next            slow = slow.next        return True</code></pre><h3 id="a-name-p142-a-LC-142-环形链表2"><a name="p142"></a> <a href="https://leetcode-cn.com/problems/linked-list-cycle-ii/" target="_blank" rel="noopener">LC-142 环形链表2</a></h3><p>给定一个链表，返回链表开始入环的第一个节点。 如果链表无环，则返回 null。</p><p>输入：head = [3,2,0,-4], pos = 1<br>输出：返回索引为 1 的链表节点<br>解释：链表中有一个环，其尾部连接到第二个节点。</p><p>f = 2s (快指针是慢指针速度的2倍)<br>f = s + nk （快指针比慢指针多走了n次环，环是k）<br>s = nk，快指针重置为head，相遇即为环的入口</p><pre><code class="language-python">class Solution:    def detectCycle(self, head: ListNode) -&gt; ListNode:        fast, slow = head, head        while True:             if not fast or not fast.next:                return None            fast = fast.next.next            slow = slow.next            if fast == slow:                break                fast = head        while fast != slow:            fast = fast.next            slow = slow.next        return slow</code></pre><h3 id="a-name-p160-a-LC-160-相交链表"><a name="p160"></a> <a href="https://leetcode-cn.com/problems/intersection-of-two-linked-lists/" target="_blank" rel="noopener">LC-160 相交链表</a></h3><p>给你两个单链表的头节点 headA 和 headB ，请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点，返回 null 。<br>图示两个链表在节点 c1 开始相交：</p><p>输入：intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3<br>输出：Intersected at ‘8’<br>解释：相交节点的值为 8 （注意，如果两个链表相交则不能为 0）。<br>从各自的表头开始算起，链表 A 为 [4,1,8,4,5]，链表 B 为 [5,0,1,8,4,5]。<br>在 A 中，相交节点前有 2 个节点；在 B 中，相交节点前有 3 个节点。</p><pre><code class="language-python">class Solution:    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -&gt; ListNode:        a = headA        b = headB        while a != b:            if a:                a = a.next            else: a = headB                    if b:                b = b.next            else: b = headA        return a</code></pre><h3 id="a-name-p19-a-LC-19-删除链表的倒数第-N-个结点"><a name="p19"></a> <a href="https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/" target="_blank" rel="noopener">LC-19 删除链表的倒数第 N 个结点</a></h3><p>给你一个链表，删除链表的倒数第 n 个结点，并且返回链表的头结点。<br>进阶：你能尝试使用一趟扫描实现吗？<br>输入：head = [1,2,3,4,5], n = 2<br>输出：[1,2,3,5]<br><img src="https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg" alt=""></p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def removeNthFromEnd(self, head: ListNode, n: int) -&gt; ListNode:        dummy = ListNode(0)        dummy.next = head         #step1: 快指针先走n步        slow, fast = dummy, dummy        for _ in range(n):            fast = fast.next         #step2: 快慢指针同时走，直到fast指针到达尾部节点，此时slow到达倒数第N个节点的前一个节点        while fast and fast.next:            slow, fast = slow.next, fast.next         #step3: 删除节点，并重新连接        slow.next = slow.next.next         return dummy.next </code></pre><h3 id="a-name-p143-a-LC-143-重排链表"><a name="p143"></a> <a href="https://leetcode-cn.com/problems/reorder-list/" target="_blank" rel="noopener">LC-143 重排链表</a></h3><p>给定一个单链表 L 的头节点 head ，单链表 L 表示为：<br>L0 → L1 → … → Ln-1 → Ln <br>请将其重新排列后变为：<br>L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …<br>不能只是单纯的改变节点内部的值，而是需要实际的进行节点交换。</p><p>输入: head = [1,2,3,4]<br>输出: [1,4,2,3]</p><p>输入: head = [1,2,3,4,5]<br>输出: [1,5,2,4,3]</p><p>三步：找中点；反转链表；合并有序链表</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def reorderList(self, head: ListNode) -&gt; None:        &quot;&quot;&quot;        Do not return anything, modify head in-place instead.        &quot;&quot;&quot;        if not head:            return None        mid = self.find_mid(head)        l1 = head        l2 = mid.next        # 切断        mid.next = None        l2 = self.reverse_list(l2)        return self.merge(l1, l2)    def find_mid(self, head):        if head is None or head.next is None:             return head        slow,fast = head, head.next        while fast and fast.next:            slow=slow.next            fast=fast.next.next        return slow    def reverse_list(self, head):        pre, cur = None, head        while cur:            nxt = cur.next            cur.next = pre            pre = cur            cur = nxt        return pre        def merge(self, l1, l2):        head = l1        while l2:            l1_nxt = l1.next            l2_nxt = l2.next            l1.next = l2            l2.next = l1_nxt            l1 = l1_nxt            l2 = l2_nxt        return head</code></pre><h3 id="a-name-p234-a-LC-234-回文链表"><a name="p234"></a> <a href="https://leetcode-cn.com/problems/palindrome-linked-list/" target="_blank" rel="noopener">LC-234 回文链表</a></h3><p>请判断一个链表是否为回文链表.<br>输入: 1-&gt;2-&gt;2-&gt;1<br>输出: true</p><p>3种方法：</p><ul><li>把链表变成数组，用双指针，缺点额外空间</li><li>链表后序递归</li><li>快慢双指针找中点后，后半段反转链表</li></ul><pre><code class="language-python"> # 第二种 class Solution:    def isPalindrome(self, head: ListNode) -&gt; bool:        self.left = head        def traverse(right):            if not right:                return True            res = traverse(right.next)            if res and self.left.val == right.val:                 res = True            else:                 res = False            self.left = self.left.next            return res        return traverse(head)</code></pre><pre><code class="language-python"># 第三种class Solution:   def isPalindrome(self, head: ListNode) -&gt; bool:       if not head or not head.next:           return True       # 找中点       fast = head       slow = head       while fast and fast.next:           slow = slow.next           fast = fast.next.next       if not fast:           mid = slow       else:           mid = slow.next       # 反转链表       right_head = mid       pre = None       cur = right_head       while cur:           nxt = cur.next           cur.next = pre           pre = cur           cur = nxt       # 比较相等       right = pre       left = head       while right and left:           if right.val != left.val:               return False           right = right.next           left = left.next       return True</code></pre><h3 id="a-name-p148-a-LC-148-排序链表"><a name="p148"></a> <a href="https://leetcode-cn.com/problems/sort-list/" target="_blank" rel="noopener">LC-148 排序链表</a></h3><p>给你链表的头结点 head ，请将其按 升序 排列并返回 排序后的链表 。<br>进阶：<br>你可以在 O(n log n) 时间复杂度和常数级空间复杂度下，对链表进行排序吗？<br>示例 1：<br>输入：head = [4,2,1,3]<br>输出：[1,2,3,4]</p><p>nlogn 复杂度，需要考虑 快排、归并、堆等</p><pre><code class="language-python">class Solution:    # 归并排序    def sortList(self, head: ListNode) -&gt; ListNode:        if not head or not head.next:             return head        left_end = self.find_mid(head)        mid = left_end.next         left_end.next = None        # 左边 None 结束，右边 mid 开始        left, right = self.sortList(head), self.sortList(mid)        return self.merged(left, right)    # 快慢指针查找链表中点    def find_mid(self, head):        if head is None or head.next is None: return head        slow,fast = head, head.next        while fast and fast.next:            slow=slow.next            fast=fast.next.next        return slow    # 合并有序链表    def merged(self, left, right):        res = ListNode()        h = res        while left and right:            if left.val &lt; right.val:                 h.next= left                left = left.nex            else:                 h.next = right                right = right.next            h = h.next        # 判断 left 和 right 是否是 None        h.next = left if left else right        return res.next</code></pre><h3 id="a-name-p82-a-LC-82-删除排序链表中的重复元素2"><a name="p82"></a> <a href="https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/" target="_blank" rel="noopener">LC-82 删除排序链表中的重复元素2</a></h3><p>存在一个按升序排列的链表，给你这个链表的头节点 head ，请你删除所有重复的元素，使每个元素 只出现一次 。<br>返回同样按升序排列的结果链表。</p><p>输入：head = [1,2,3,3,4,4,5]<br>输出：[1,2,5]</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def deleteDuplicates(self, head: ListNode) -&gt; ListNode:        dummy = ListNode(None)        dummy.next = head        p = dummy        while head and head.next:            if p.next.val != head.next.val:                p = p.next                head = head.next            else:                while head and head.next and p.next.val==head.next.val:                    head = head.next                # p 的 next 要指定 head 的下一个                p.next = head.next                head = head.next        return dummy.next</code></pre><p>组织 tail，2 步 head next 避免 插入相同的</p><pre><code class="language-python">class Solution:    def deleteDuplicates(self, head: ListNode) -&gt; ListNode:        tail = dummy = ListNode(-1)        while head:            if head.next == None or head.val != head.next.val:                tail.next = head;                tail = tail.next            while head.next and head.val == head.next.val:                head = head.next            head = head.next        tail.next = None        return dummy.next</code></pre><h3 id="a-name-p83-a-LC-83-删除排序链表中的重复元素"><a name="p83"></a> <a href="https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/" target="_blank" rel="noopener">LC-83 删除排序链表中的重复元素</a></h3><p>存在一个按升序排列的链表，给你这个链表的头节点 head ，请你删除所有重复的元素，使每个元素 只出现一次 。<br>返回同样按升序排列的结果链表。</p><p>输入：head = [1,1,2,3,3]<br>输出：[1,2,3]</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def deleteDuplicates(self, head: ListNode) -&gt; ListNode:        if not head:            return None        dummy = ListNode(-10000)        dummy.next = head        p = dummy        # p 落后 head 一个节点位置        while head:            if p.val != head.val:                p.next = head                p = p.next            head = head.next        p.next = None        return dummy.next</code></pre><h3 id="a-name-p24-a-LC-24-两两交换链表中的节点"><a name="p24"></a> <a href="https://leetcode-cn.com/problems/swap-nodes-in-pairs/" target="_blank" rel="noopener">LC-24 两两交换链表中的节点</a></h3><p>给定一个链表，两两交换其中相邻的节点，并返回交换后的链表。<br>你不能只是单纯的改变节点内部的值，而是需要实际的进行节点交换。<br>示例 1：<br>输入：head = [1,2,3,4]<br>输出：[2,1,4,3]</p><p>递归：</p><pre><code class="language-python">class Solution:    def swapPairs(self, head: ListNode) -&gt; ListNode:        if not head or not head.next:            return headx        newHead = head.next        head.next = self.swapPairs(newHead.next)        newHead.next = head        return newHead</code></pre><p>迭代</p><pre><code class="language-python">class Solution:    def swapPairs(self, head: ListNode) -&gt; ListNode:        dummyHead = ListNode(0)        dummyHead.next = head        temp = dummyHead        while temp.next and temp.next.next:            node1 = temp.next            node2 = temp.next.next            temp.next = node2            node1.next = node2.next            node2.next = node1            temp = node1        return dummyHead.next</code></pre><h3 id="a-name-p328-a-LC-328-奇偶链表"><a name="p328"></a> <a href="https://leetcode-cn.com/problems/odd-even-linked-list/" target="_blank" rel="noopener">LC-328 奇偶链表</a></h3><p>给定一个单链表，把所有的奇数节点和偶数节点分别排在一起。请注意，这里的奇数节点和偶数节点指的是节点编号的奇偶性，而不是节点的值的奇偶性。<br>请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1)，时间复杂度应为 O(nodes)，nodes 为节点总数。</p><p>示例 1:<br>输入: 1-&gt;2-&gt;3-&gt;4-&gt;5-&gt;NULL<br>输出: 1-&gt;3-&gt;5-&gt;2-&gt;4-&gt;NULL<br>示例 2:<br>输入: 2-&gt;1-&gt;3-&gt;5-&gt;6-&gt;4-&gt;7-&gt;NULL<br>输出: 2-&gt;3-&gt;6-&gt;7-&gt;1-&gt;5-&gt;4-&gt;NULL</p><pre><code class="language-python"># class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def oddEvenList(self, head: ListNode) -&gt; ListNode:        if not head:            return None                odd = head        even = head.next        evenhead = head.next        while even and even.next:            odd.next = even.next            odd = odd.next            even.next = odd.next            even = even.next                odd.next = evenhead        return head</code></pre><h3 id="a-name-p61-a-LC-61-旋转链表"><a name="p61"></a> <a href="https://leetcode-cn.com/problems/rotate-list/" target="_blank" rel="noopener">LC-61 旋转链表</a></h3><p>给你一个链表的头节点 head ，旋转链表，将链表每个节点向右移动 k 个位置。<br>示例 1：<br><img src="https://assets.leetcode.com/uploads/2020/11/13/rotate1.jpg" alt=""><br>输入：head = [1,2,3,4,5], k = 2<br>输出：[4,5,1,2,3]</p><p>分解为3个题：1.长度，2.末尾第n个节点，3.切割链表</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def rotateRight(self, head: Optional[ListNode], k: int) -&gt; Optional[ListNode]:        length = 0        cur = head        while cur:            length += 1            cur = cur.next        if length &lt; 1: # 注意            return head        k = k % length        if k == 0:             return head        dummy = ListNode(0)        dummy.next = head        fast, slow = dummy, dummy        for _ in range(k):            fast = fast.next        while fast.next:            slow = slow.next            fast = fast.next        newHead = slow.next        slow.next = None        fast.next = head        return newHead</code></pre><h3 id="a-name-p86-a-LC-86-分隔链表"><a name="p86"></a> <a href="https://leetcode-cn.com/problems/partition-list/" target="_blank" rel="noopener">LC-86 分隔链表</a></h3><p>给你一个链表的头节点 head 和一个特定值 x ，请你对链表进行分隔，使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。<br>你应当 保留 两个分区中每个节点的初始相对位置。<br>示例 1：<br>输入：head = [1,4,3,2,5,2], x = 3<br>输出：[1,2,2,4,3,5]</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def partition(self, head: ListNode, x: int) -&gt; ListNode:        small, large = ListNode(), ListNode()        sm = small        lg = large        while head:            if head.val &lt; x:                sm.next = head                sm = sm.next            else:                lg.next = head                lg = lg.next            head = head.next        lg.next = None        sm.next = large.next        return small.next</code></pre><h3 id="a-name-p382-a-LC-382-链表随机节点"><a name="p382"></a> <a href="https://leetcode-cn.com/problems/linked-list-random-node/" target="_blank" rel="noopener">LC-382 链表随机节点</a></h3><p>给定一个单链表，随机选择链表的一个节点，并返回相应的节点值。保证每个节点被选的概率一样。<br>进阶:<br>如果链表十分大且长度未知，如何解决这个问题？你能否使用常数级空间复杂度实现？<br>示例:<br>// 初始化一个单链表 [1,2,3].<br>ListNode head = new ListNode(1);<br>head.next = new ListNode(2);<br>head.next.next = new ListNode(3);<br>Solution solution = new Solution(head);<br>// getRandom()方法应随机返回1,2,3中的一个，保证每个元素被返回的概率相等。<br>solution.getRandom();</p><p>蓄水池采样，每次前进概率为 1/k，如果没有采样到，则前述保留的概率为 1/k * k/k+1 …<br>也为 1/k。</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextimport randomclass Solution:    def __init__(self, head: ListNode):        self.head = head            def getRandom(self) -&gt; int:        count = 0        reserve = 0        cur = self.head        while cur:            count += 1            rand = random.randint(1,count)            if rand == count:                reserve = cur.val            # 1 * 1/2 * 2/3 * 3/4 ...            cur = cur.next        return reserve</code></pre><h1>数学</h1><h3 id="a-name-g1-a-高频-1-计算π"><a name="g1"></a> <a href="">高频-1 计算π</a></h3><p>假设循环次数可以无限，如何得到比较精确的π。</p><ol><li>蒙特卡洛估计法，正方形中随机采样看落在圆内的概率</li></ol><pre><code class="language-python">import randomdef monte_carlo_pi(num_samples):    inside_circle = 0    for _ in range(num_samples):        x, y = random.random(), random.random()  # 生成两个随机数 x 和 y，范围在 [0, 1) 之间        if x**2 + y**2 &lt;= 1:  # 检查点 (x, y) 是否在单位圆内            inside_circle += 1  # 如果在圆内，计数器加 1    return (inside_circle / num_samples) * 4  # 计算落在圆内的点的比例，并乘以 4 估算 πnum_samples = 1000000pi_estimate = monte_carlo_pi(num_samples)print(f&quot;Estimated Pi: {pi_estimate}&quot;)</code></pre><ol start="2"><li>莱布尼茨法，无限级数=π/4</li></ol><pre><code class="language-python">def process(n):    pie = 0    for i in range(n):        numerater = (-1) ** i        denumerater = 2 * i + 1        number = numerater / denumerater        pie += number    return 4 * pieres = process(100000)print(res)</code></pre><h3 id="a-name-p69-a-LC-69-x-的平方根"><a name="p69"></a> <a href="https://leetcode.cn/problems/sqrtx/description/" target="_blank" rel="noopener">LC-69 x 的平方根</a></h3><p>给你一个非负整数 x ，计算并返回 x 的 算术平方根。由于返回类型是整数，结果只保留 整数部分，小数部分将被舍去.<br>注意：不允许使用任何内置指数函数和算符，例如 pow(x, 0.5) 或者 x ** 0.5<br>示例 1：<br>输入：x = 4 输出：2<br>示例 2：<br>输入：x = 8<br>输出：2<br>解释：8 的算术平方根是 2.82842…, 由于返回类型是整数，小数部分将被舍去。</p><pre><code class="language-python">class Solution(object):    def mySqrt(self, x):        &quot;&quot;&quot;        :type x: int        :rtype: int        &quot;&quot;&quot;        left, right = 0, x        while left &lt;= right:            mid = left + (right - left) // 2            if mid ** 2 == x:                return mid            if mid ** 2 &lt; x:                left = mid + 1            else:                right = mid - 1        return left - 1</code></pre><h3 id="a-name-p2-a-LC-2-两数相加"><a name="p2"></a> <a href="https://leetcode-cn.com/problems/add-two-numbers/" target="_blank" rel="noopener">LC-2 两数相加</a></h3><p>给你两个 非空 的链表，表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的，并且每个节点只能存储 一位 数字。<br>请你将两个数相加，并以相同形式返回一个表示和的链表。<br>你可以假设除了数字 0 之外，这两个数都不会以 0 开头。</p><p>输入：l1 = [2,4,3], l2 = [5,6,4]<br>输出：[7,0,8]<br>解释：342 + 465 = 807.</p><p>输入：l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]<br>输出：[8,9,9,9,0,0,0,1]</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, val=0, next=None):#         self.val = val#         self.next = nextclass Solution:    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -&gt; ListNode:        head = dummy = ListNode()        curry = 0        sum = 0        res = 0        # 当l1和l2都为None时，需要检查curry进位符是否还为1        while curry or l1 or l2:            if l1:                 l1_val = l1.val             else:                 l1_val = 0            if l2:                 l2_val = l2.val             else:                 l2_val = 0            sum = l1_val + l2_val + curry            res = sum % 10            curry = sum // 10            dummy.next = ListNode(res)            dummy, l1, l2 = dummy.next, l1.next if l1 else None, l2.next if l2 else None        return head.next</code></pre><p>链表需要时刻检查链表是否为None，新建2个dummy节点，一个随迭代往后，一个指向开头用于返回结果。</p><h3 id="a-name-p8-a-LC-8-字符串转换整数-atoi"><a name="p8"></a> <a href="https://leetcode-cn.com/problems/string-to-integer-atoi/" target="_blank" rel="noopener">LC-8 字符串转换整数 (atoi)</a></h3><p>请你来实现一个 myAtoi(string s) 函数，使其能将字符串转换成一个 32 位有符号整数（类似 C/C++ 中的 atoi 函数）。</p><p>函数 myAtoi(string s) 的算法如下：<br>读入字符串并丢弃无用的前导空格<br>检查下一个字符（假设还未到字符末尾）为正还是负号，读取该字符（如果有）。 确定最终结果是负数还是正数。 如果两者都不存在，则假定结果为正。<br>读入下一个字符，直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。<br>将前面步骤读入的这些数字转换为整数（即，“123” -&gt; 123， “0032” -&gt; 32）。如果没有读入数字，则整数为 0 。必要时更改符号（从步骤 2 开始）。<br>如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ，需要截断这个整数，使其保持在这个范围内。具体来说，小于 −231 的整数应该被固定为 −231 ，大于 231 − 1 的整数应该被固定为 231 − 1 。<br>返回整数作为最终结果。</p><p>输入：s = &quot;   -42&quot;<br>输出：-42<br>解释：<br>第 1 步：&quot;   -42&quot;（读入前导空格，但忽视掉）<br>^<br>第 2 步：&quot;   -42&quot;（读入 ‘-’ 字符，所以结果应该是负数）<br>^<br>第 3 步：&quot;   -42&quot;（读入 “42”）<br>^<br>解析得到整数 -42 。<br>由于 “-42” 在范围 [-231, 231 - 1] 内，最终结果为 -42 。</p><p>需要严格注意这个读入的顺序</p><pre><code class="language-python">class Solution:    def myAtoi(self, s: str) -&gt; int:        str=s.lstrip()        if len(str)==0:            return 0        # 不断改写        last=0        #如果有符号设置起始位置2，其余的为1        i=2 if str[0]=='-'or str[0]=='+'  else 1        #循环，直到无法强转成int，跳出循环        while i &lt;= len(str):            try:                last=int(str[:i])                i+=1            except:                break        if last&lt;-2**31:            return -2147483648        if last&gt;2**31-1:            return 2147483647        return last</code></pre><p>启发另一种索引方式，索引最后一个位置，而非索引对应位置，这样可以和题目要求对应，作用到下一个之前</p><h3 id="a-name-p50-a-LC-50-Pow-x-n"><a name="p50"></a> <a href="https://leetcode-cn.com/problems/powx-n/" target="_blank" rel="noopener">LC-50 Pow(x, n)</a></h3><p>实现 pow(x, n) ，即计算 x 的 n 次幂函数（即，xn）。<br>示例 1:<br>输入：x = 2.00000, n = 10<br>输出：1024.00000</p><p>示例 3：<br>输入：x = 2.00000, n = -2<br>输出：0.25000<br>解释：2-2 = 1/22 = 1/4 = 0.25</p><pre><code class="language-python">class Solution:    def myPow(self, x: float, n: int) -&gt; float:        def quickMul(N):            if N == 0:                return 1.0            y = quickMul(N // 2)            return y * y if N % 2 == 0 else y * y * x                return quickMul(n) if n &gt;= 0 else 1.0 / quickMul(-n)</code></pre><p>快速幂模板：</p><pre><code class="language-python"># 实数def qpow(a, n):    ans = 1    while n &gt; 0:        if (n &amp; 1):            ans *= a        a *= a        n &gt;&gt;= 1    return ans# 矩阵import numpy as npdef pow(n):    a = np.array([[1,0],[0,1]])    b = np.array([[1,1],[1,0]])    while(n &gt; 0):        if (n % 2 == 1):            a = np.dot(b, a)        b = np.dot(b, b)        n &gt;&gt;= 1    return a[0][1]class Solution:    def myPow(self, x: float, n: int) -&gt; float:        if n &lt; 0:            x, n = 1/x, n*(-1)        return qpow(x, n)</code></pre><h3 id="a-name-p400-a-LC-400-第-N-位数字"><a name="p400"></a> <a href="https://leetcode-cn.com/problems/powx-n/" target="_blank" rel="noopener">LC-400 第 N 位数字</a></h3><p>给你一个整数 n ，请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位数字。<br>示例 1：<br>输入：n = 3<br>输出：3<br>示例 2：<br>输入：n = 11<br>输出：0<br>解释：第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, … 里是 0 ，它是 10 的一部分。</p><p>第一行共有 10<em>9</em>2 个数<br>第二行公有 10<em>10</em>9<em>3 个数<br>第三行公有 10</em>10<em>10</em>9*4 个数</p><p>好了规律出来了:<br>假设初始标记count = 1<br>每行总数字为：10**count * 9 * (count+1)</p><p>另外关于整除后的余数：<br>如果没有余数，那结果就是基数（10<strong>count） + 商 - 1，然后获取这个数的最后一个数字即可<br>如果有余数，那结果就是基数（10</strong>count） + 商，获取到当前的数字，然后 余数-1 找到对应index返回即可</p><pre><code class="language-python">class Solution:    def findNthDigit(self, n: int) -&gt; int:        if n &lt; 10:            return n        n -= 9        count = 1        while True:            num = 10 ** count * 9 * (count + 1)            if n &gt; num:                n -= num                count += 1            else:                i, j = divmod(n, (count + 1))                if not j:                    return int(str(10 ** count + i - 1)[-1])                else:                    return int(str(10 ** count + i)[j - 1])</code></pre><h3 id="a-name-p43-a-LC-43-字符串相乘"><a name="p43"></a> <a href="https://leetcode-cn.com/problems/multiply-strings/" target="_blank" rel="noopener">LC-43 字符串相乘</a></h3><p>给定两个以字符串形式表示的非负整数 num1 和 num2，返回 num1 和 num2 的乘积，它们的乘积也表示为字符串形式。<br>示例 1:<br>输入: num1 = “2”, num2 = “3”<br>输出: “6”</p><p>示例 2:<br>输入: num1 = “123”, num2 = “456”<br>输出: “56088”</p><pre><code class="language-python">class Solution:    def multiply(self, num1: str, num2: str) -&gt; str:        n1 = len(num1)        n2 = len(num2)        num2_int = int(num2)        res = 0        #n1 按位倒序与n2相乘，拿到对应位置作为10的power        for i in range(n1-1, -1, -1):            weight = n1- 1 - i            num1_new = int(num1[i])            res += num1_new * 10 ** weight * num2_int        return str(res)</code></pre><h3 id="a-name-p470-a-LC-470-用-Rand7-实现-Rand10"><a name="p470"></a> <a href="https://leetcode-cn.com/problems/implement-rand10-using-rand7/" target="_blank" rel="noopener">LC-470 用 Rand7() 实现 Rand10()</a></h3><p>已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数，试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。<br>不要使用系统的 Math.random() 方法.<br>示例 1:<br>输入: 1<br>输出: [7]<br>示例 2:<br>输入: 2<br>输出: [8,4]</p><p>小数表示大数：(rand7()-1) × 7 + rand7() = result<br>(rand_x() - 1) × origin_number + rand_y()<br>可以等概率的生成[1, X * Y]范围的随机数</p><pre><code class="language-python">class Solution:    def rand10(self):        &quot;&quot;&quot;        :rtype: int        &quot;&quot;&quot;        # 已知 rand_N() 可以等概率的生成[1, N]范围的随机数        # 那么：        # (rand_X() - 1) × Y + rand_Y() ==&gt; 可以等概率的生成[1, X * Y]范围的随机数        # 即实现了 rand_XY()        while True:            num = (rand7() - 1) * 7 + rand7()            if num &lt;= 40:                return num % 10 + 1</code></pre><h3 id="a-name-p12-a-LC-12-整数转罗马数字"><a name="p12"></a> <a href="https://leetcode-cn.com/problems/integer-to-roman/" target="_blank" rel="noopener">LC-12 整数转罗马数字</a></h3><p>罗马数字包含以下七种字符： I， V， X， L，C，D 和 M。<br>字符          数值<br>I             1<br>V             5<br>X             10<br>L             50<br>C             100<br>D             500<br>M             1000<br>例如， 罗马数字 2 写做 II ，即为两个并列的 1。12 写做 XII ，即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。</p><p>通常情况下，罗马数字中小的数字在大的数字的右边。但也存在特例，例如 4 不写做 IIII，而是 IV。数字 1 在数字 5 的左边，所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地，数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况：<br>I 可以放在 V (5) 和 X (10) 的左边，来表示 4 和 9。<br>X 可以放在 L (50) 和 C (100) 的左边，来表示 40 和 90。 <br>C 可以放在 D (500) 和 M (1000) 的左边，来表示 400 和 900。<br>给你一个整数，将其转为罗马数字。</p><p>哈希表</p><pre><code class="language-python">class Solution:    def intToRoman(self, num: int) -&gt; str:        hashmap = {1000:'M',             900:'CM',             500:'D',             400:'CD',             100:'C',             90:'XC',             50:'L',             40:'XL',             10:'X',             9:'IX',             5:'V',             4:'IV',             1:'I'        }        res = ''        for i in hashmap.keys():            count = num // i            if count &gt; 0:                res += ''.join([hashmap[i]] * count)                num = num % i        return res</code></pre><h1>滑动窗口</h1><p><strong>滑动窗口通用模板：</strong></p><pre><code class="language-python">need, window = {}, {}for c in t:    记录need            # 视具体问题而定，用于判断窗口包含的字串是否满足题目要求left, right = 0, 0      # 初始化左右指针，窗口是左闭右开区间，[left, right)while right &lt; len(s):    c = s[right]    right += 1    更新窗口数据    while 满足窗口收缩条件：        记录优化后的结果        d = s[left]        left += 1        更新窗口数据return 结果</code></pre><h3 id="a-name-p3-a-LC-3-无重复字符的最长子串"><a name="p3"></a> <a href="https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/" target="_blank" rel="noopener">LC-3 无重复字符的最长子串</a></h3><p>给定一个字符串 s ，请你找出其中不含有重复字符的 最长子串 的长度。<br>示例 1:<br>输入: s = “abcabcbb”<br>输出: 3<br>解释: 因为无重复字符的最长子串是 “abc”，所以其长度为 3。</p><pre><code class="language-python">class Solution:    def lengthOfLongestSubstring(self, s: str) -&gt; int:        res = 0        window = dict()        left, right = 0, 0        while right &lt; len(s):            cur = s[right]            # 先加1往后走            window[cur] = window.get(cur, 0) + 1            right += 1            # 更新            while window[cur] &gt; 1:                if s[left] in window.keys():                    window[s[left]] -= 1                left += 1            res = max(res, right - left)        return res        </code></pre><h3 id="a-name-p76-a-LC-76-最小覆盖子串"><a name="p76"></a> <a href="https://leetcode-cn.com/problems/minimum-window-substring/" target="_blank" rel="noopener">LC-76 最小覆盖子串</a></h3><p>给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串，则返回空字符串 “” 。<br>输入：s = “ADOBECODEBANC”, t = “ABC”<br>输出：“BANC”</p><p>滑动窗口的基本模式：</p><ul><li>初始化的 min_len 为 inf float</li><li>记住只有在need字典中的key才会影响最终结果，不在的话就直接right++或者left–</li><li>match标志位表示这一位的need和window的key是不是值相等</li></ul><pre><code class="language-python">class Solution:    def minWindow(self, s: str, t: str) -&gt; str:        # 初始化        start, min_len = 0, float('Inf')        left, right = 0, 0        need = Counter(t)        window = collections.defaultdict(int)        match = 0        # right         while right &lt; len(s):            # 拿出 right 指针            c1 = s[right]            if need[c1] &gt; 0:                # 匹配后match+1                window[c1] += 1                if window[c1] == need[c1]:                    match += 1            right += 1            while match == len(need):                # 更新结果                if right - left &lt; min_len:                    min_len = right - left                    start = left                # 优化结果，更新window字典                c2 = s[left]                if need[c2] &gt; 0:                    window[c2] -= 1                    if window[c2] &lt; need[c2]:                        match -= 1                left += 1        return s[start:start+min_len] if min_len != float('Inf') else &quot;&quot;</code></pre><h3 id="a-name-p239-a-LC-239-滑动窗口最大值"><a name="p239"></a> <a href="https://leetcode-cn.com/problems/sliding-window-maximum/" target="_blank" rel="noopener">LC-239 滑动窗口最大值</a></h3><p>给你一个整数数组 nums，有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。<br>返回滑动窗口中的最大值。<br>示例 1：<br>输入：nums = [1,3,-1,-3,5,3,6,7], k = 3<br>输出：[3,3,5,5,6,7]<br>解释：<br>滑动窗口的位置                最大值</p><hr><p>[1  3  -1] -3  5  3  6  7       3<br>1 [3  -1  -3] 5  3  6  7       3<br>1  3 [-1  -3  5] 3  6  7       5<br>1  3  -1 [-3  5  3] 6  7       5<br>1  3  -1  -3 [5  3  6] 7       6<br>1  3  -1  -3  5 [3  6  7]      7</p><p>单调队列，新的元素，检查是否大于最后一个，满足pop掉末尾的，上浮；判断第一个是否不在窗口，popleft。</p><pre><code class="language-python">class Solution:    def maxSlidingWindow(self, nums: List[int], k: int) -&gt; List[int]:        from collections import deque        window = deque()        res = []        for i in range(len(nums)):            while window and nums[i] &gt; nums[window[-1]]:                window.pop()            window.append(i)            if window[0] &lt;= i - k:                window.popleft()                        if i - k + 1 &gt;= 0:                res.append(nums[window[0]])        return res</code></pre><h3 id="a-name-p209-a-LC-209-长度最小的子数组"><a name="p209"></a> <a href="https://leetcode-cn.com/problems/minimum-size-subarray-sum/" target="_blank" rel="noopener">LC-209 长度最小的子数组</a></h3><p>给定一个含有 n 个正整数的数组和一个正整数 target 。</p><p>找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ，并返回其长度。如果不存在符合条件的子数组，返回 0 。</p><p>示例 1：<br>输入：target = 7, nums = [2,3,1,2,4,3]<br>输出：2<br>解释：子数组 [4,3] 是该条件下的长度最小的子数组。</p><p>经典滑动窗口题，用left和right来划定窗口</p><pre><code class="language-python">class Solution:    def minSubArrayLen(self, target: int, nums: List[int]) -&gt; int:        left, right = 0, 0        bst = float('Inf')                while right &lt; len(nums):            cur = nums[right]            sum_ans = sum(nums[left:right+1])            if sum_ans &lt; target:                right += 1            while sum_ans &gt;= target and left &lt;= right:                gap = right - left + 1                left += 1                sum_ans = sum(nums[left:right+1])                if gap &lt; bst:                    bst = gap                if bst == float('Inf'):            return 0        else: return bst</code></pre><h3 id="a-name-p340-a-LC-340-至多包含k个不同字符的最长子串"><a name="p340"></a> <a href="https://blog.csdn.net/ft_sunshine/article/details/103482217/" target="_blank" rel="noopener">LC-340 至多包含k个不同字符的最长子串</a></h3><p>给定一个字符串 s ，找出 至多 包含 k 个不同字符的最长子串 T。<br>示例1：<br>输入: s = “eceba”, k = 2<br>输出: 3<br>解释: 则 T 为 “ece”，所以长度为 3。</p><pre><code class="language-python">class Solution:    def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -&gt; int:        Dict = {}        count = 0        left, right = 0, 0        res = 0        while(right &lt; len(s)):            if s[right] not in Dict:                Dict[s[right]] = 1            else:                Dict[s[right]] += 1            if Dict[s[right]] == 1:                count += 1            # 小于k是结果            if count &lt;= k:                res = max(res, right - left + 1)            # 大于k是需要优化的结果            else:                       while(count &gt; k and left &lt;= right):                    Dict[s[left]] -= 1                    if Dict[s[left]] == 0:                        count -= 1                    left += 1                # 优化后再更新一遍结果                res = max(res, right - left + 1)            # 继续向后遍历            right += 1        return res</code></pre><h1>二分查找</h1><p><strong>二分查找模型：</strong></p><pre><code class="language-python"># 常规def binarySearch(nums, target):    if len(nums) == 0: return -1    left, right = 0, len(nums) - 1    while(left &lt;= right):        mid = left + (right - left) // 2        if nums[mid] == target:            return mid        elif nums[mid] &gt; target:            right = mid - 1 # -1 强制保证        elif nums[mid] &lt; target:            left = mid + 1    return -1# 最左边界, right = mid，返回leftdef binarySearchLeft(nums, target):    if len(nums) == 0: return -1    left, right = 0, len(nums) - 1     while(left &lt;= right):         mid = left + (right - left) // 2        if nums[mid] &gt;= target:            right = mid - 1        else:             left = mid + 1    if left &gt;= len(nums) or nums[left] != target:        return -1    return left# 最右边界，def binarySearchRight(nums, target):    if len(nums) == 0: return -1    left, right = 0, len(nums) - 1     while (left &lt;= right):        mid = left + (right - left) // 2        if nums[mid] &lt;= target:            left = mid + 1        else:            right = mid - 1    if right &lt; 0 or nums[right] != target:        return -1    return right</code></pre><h3 id="a-name-p33-a-LC-33-搜索旋转排序数组"><a name="p33"></a> <a href="https://leetcode-cn.com/problems/search-in-rotated-sorted-array/" target="_blank" rel="noopener">LC-33 搜索旋转排序数组</a></h3><p>整数数组 nums 按升序排列，数组中的值 互不相同 。<br>在传递给函数之前，nums 在预先未知的某个下标 k（0 &lt;= k &lt; nums.length）上进行了 旋转，使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]]（下标 从 0 开始 计数）。例如， [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你旋转后 的数组 nums 和一个整数 target ，如果 nums 中存在这个目标值 target ，则返回它的下标，否则返回 -1 。</p><p>示例 1：<br>输入：nums = [4,5,6,7,0,1,2], target = 0<br>输出：4</p><p>示例 2：<br>输入：nums = [4,5,6,7,0,1,2], target = 3<br>输出：-1</p><pre><code class="language-python">class Solution(object):    def search(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: int        &quot;&quot;&quot;        left = 0        right = len(nums) - 1        while left &lt;= right:            mid = left + (right - left) // 2            if nums[mid] == target:                return mid            if nums[mid] &gt;= nums[left]:                # 在左半区间，要定新的right，left一定带等于                # [left, target, mid] right = mid                # [left, target, mid) right = mid - 1                if nums[left] &lt;= target and target &lt;= nums[mid]:                    right = mid                # 在右半区间，要定新的left，                # (mid, target, right), left = mid + 1                else:                     left = mid + 1            else:                # 在右半区间，要定新的left，right一定带等于                # (mid, target, right] left = mid + 1                # [mid, target, right] left = mid                if nums[mid] &lt; target and target &lt;= nums[right]:                    left = mid + 1                # 在左半区间，要定新的right                # (lfet, target, right), right = mid - 1                else:                    right = mid - 1        return -1</code></pre><h3 id="a-name-p162-a-LC-163-寻找峰值"><a name="p162"></a> <a href="https://leetcode-cn.com/problems/find-peak-element/" target="_blank" rel="noopener">LC-163 寻找峰值</a></h3><p>峰值元素是指其值大于左右相邻值的元素。<br>给你一个输入数组 nums，找到峰值元素并返回其索引。数组可能包含多个峰值，在这种情况下，返回 任何一个峰值 所在位置即可。<br>你可以假设 nums[-1] = nums[n] = -∞ 。</p><p>示例 2：<br>输入：nums = [1,2,1,3,5,6,4]<br>输出：1 或 5<br>解释：你的函数可以返回索引 1，其峰值元素为 2；或者返回索引 5，其峰值元素为 6。</p><pre><code class="language-python">class Solution:    def findPeakElement(self, nums: List[int]) -&gt; int:        if len(nums) &lt;= 2:            return nums.index(max(nums))        left = 0        right = len(nums) - 1        while left &lt; right:            mid = (left + right) // 2            if nums[mid] &gt; nums[mid + 1]:                right = mid            else:                left = mid + 1        return left</code></pre><h3 id="a-name-p240-a-LC-240-搜索二维矩阵-II"><a name="p240"></a> <a href="https://leetcode-cn.com/problems/search-a-2d-matrix-ii/" target="_blank" rel="noopener">LC-240 搜索二维矩阵 II</a></h3><p>编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性：<br>每行的元素从左到右升序排列。<br>每列的元素从上到下升序排列。<br> <br>输入：matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5<br>输出：true</p><p>Trick: 以右上角的值作为起点，大于target往下走，小于target往左走。</p><pre><code class="language-python">class Solution:    def searchMatrix(self, matrix: List[List[int]], target: int) -&gt; bool:        if len(matrix) &lt;= 1 and len(matrix[0]) &lt;= 1:            return matrix[0][0] == target        row, col = 0, len(matrix[0]) - 1        while 0 &lt;= row &lt; len(matrix) and 0 &lt;= col &lt; len(matrix[0]):            if target &lt; matrix[row][col]:                col -= 1            elif target &gt; matrix[row][col]:                row += 1            else:                return True        return False</code></pre><h3 id="a-name-p31-a-LC-31-下一个排列"><a name="p31"></a> <a href="https://leetcode-cn.com/problems/next-permutation/" target="_blank" rel="noopener">LC-31 下一个排列</a></h3><p>实现获取 下一个排列 的函数，算法需要将给定数字序列重新排列成字典序中下一个更大的排列（即，组合出下一个更大的整数）。<br>如果不存在下一个更大的排列，则将数字重新排列成最小的排列（即升序排列）。必须原地修改，只允许使用额外常数空间。</p><p>示例 1：<br>输入：nums = [1,2,3]<br>输出：[1,3,2]<br>示例 2：<br>输入：nums = [3,2,1]<br>输出：[1,2,3]</p><p>需要原地排序，</p><ol><li>找到第一个 左边 &lt; 右边 的左边index i</li><li>从尾部开始遍历，找到第一个 右边 &gt; 左边i的 index j</li><li>交换 nums[i] 和 nums[j]</li><li>对于 i+1 部分整体反转</li></ol><pre><code class="language-python">class Solution:    def nextPermutation(self, nums):        # 1.从后往前找，找相邻2个升序(i, i+1)        i = len(nums) - 2        while i &gt;= 0 and nums[i] &gt;= nums[i+1]:            i -= 1        # 2. 防止整体都要换位置        if i &gt;= 0:            j = len(nums) - 1            # 3. 从后往前找，第一个比i大的j            while nums[j] &lt;= nums[i]:                j -= 1            nums[i], nums[j] = nums[j], nums[i]                # 4. 排序 i+1 后面的部分，整体反转        nums[i+1:] = sorted(nums[i+1:])</code></pre><h3 id="a-name-p34-a-LC-34-在排序数组中查找元素的第一个和最后一个位置"><a name="p34"></a> <a href="https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/" target="_blank" rel="noopener">LC-34 在排序数组中查找元素的第一个和最后一个位置</a></h3><p>给定一个按照升序排列的整数数组 nums，和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。<br>如果数组中不存在目标值 target，返回 [-1, -1]。</p><p>示例 1：<br>输入：nums = [5,7,7,8,8,10], target = 8<br>输出：[3,4]</p><pre><code class="language-python">class Solution:    def searchRange(self, nums: List[int], target: int) -&gt; List[int]:        def leftbound(nums, target):            if len(nums) == 0: return -1            left, right = 0, len(nums)            while(left &lt; right):                mid = left + (right - left) // 2                if nums[mid] &gt;= target:                    right = mid                else:                    left = mid + 1            return left        def rightbound(nums, target):            if len(nums) == 0: return -1            left, right = 0, len(nums)            while(left &lt; right):                mid = left + (right - left) // 2                if nums[mid] &lt;= target:                    left = mid + 1                else:                    right = mid            return left - 1        lb = leftbound(nums, target)        rb = rightbound(nums, target)        if len(nums) == 0:            return [-1, -1]        if lb == len(nums) or nums[lb] != target:            return [-1, -1]        return [lb, rb]</code></pre><h3 id="a-name-p287-a-LC-287-寻找重复数"><a name="p287"></a> <a href="https://leetcode-cn.com/problems/find-the-duplicate-number/" target="_blank" rel="noopener">LC-287 寻找重复数</a></h3><p>给定一个包含 n + 1 个整数的数组 nums ，其数字都在 1 到 n 之间（包括 1 和 n），可知至少存在一个重复的整数。<br>假设 nums 只有 一个重复的整数 ，找出 这个重复的数 。<br>你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。<br>示例 1：<br>输入：nums = [1,3,4,2,2]<br>输出：2</p><p>示例 2：<br>输入：nums = [3,1,3,4,2]<br>输出：3</p><p>注意条件是n+1个在1到n之间的数，比较和计算小于mid的坐标数，来确定重复在左还是在右，抽屉原理</p><pre><code class="language-python">class Solution:    def findDuplicate(self, nums: List[int]) -&gt; int:        left = 0        right = len(nums) - 1                while left &lt; right:            mid = left + (right - left ) // 2            # 计数， 找小于等于mid的个数            cnt = 0            for num in nums:                if num &lt;= mid:                    cnt += 1            # &lt;= 说明 重复元素在右半边            if cnt &lt;=  mid:                left  = mid + 1            else:                right = mid        return left</code></pre><h3 id="a-name-p153-a-LC-153-寻找旋转排序数组中的最小值"><a name="p153"></a> <a href="https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/" target="_blank" rel="noopener">LC-153 寻找旋转排序数组中的最小值</a></h3><p>已知一个长度为 n 的数组，预先按照升序排列，经由 1 到 n 次 旋转 后，得到输入数组。例如，原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到：<br>若旋转 4 次，则可以得到 [4,5,6,7,0,1,2]<br>若旋转 7 次，则可以得到 [0,1,2,4,5,6,7]<br>注意，数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。</p><p>给你一个元素值 互不相同 的数组 nums ，它原来是一个升序排列的数组，并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。<br>你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。</p><p>示例 1：<br>输入：nums = [3,4,5,1,2]<br>输出：1<br>解释：原数组为 [1,2,3,4,5] ，旋转 3 次得到输入数组。</p><p>最小值：</p><pre><code class="language-python">class Solution:    def findMin(self, nums: List[int]) -&gt; int:        # 左闭右闭区间，如果用右开区间则不方便判断右值        left, right = 0, len(nums) - 1                  while left &lt; right:                                 # 中值 &gt; 右值，最小值在右半边，收缩左边界            mid = left + (right - left) // 2                     if nums[mid] &gt; nums[right]:                    # 因为中值 &gt; 右值，中值肯定不是最小值，左边界可以跨过mid                     left = mid + 1                       # 明确中值 &lt; 右值，最小值在左半边，收缩右边界                   elif nums[mid] &lt; nums[right]:                       # 因为中值 &lt; 右值，中值也可能是最小值，右边界只能取到mid处                right = mid                             return nums[left]                       </code></pre><p>存在重复时的最小值 (lc-154)：</p><pre><code class="language-python">class Solution:    def findMin(self, nums: List[int]) -&gt; int:        left, right = 0, len(nums) - 1                         while left &lt; right:                                 mid = left + (right - left) // 2              if nums[mid] &gt; nums[right]:                       left = mid + 1                             elif nums[mid] &lt; nums[right]:                     right = mid                                 else:                right -= 1        return nums[left]</code></pre><p>最大值：</p><pre><code class="language-python">class Solution:    def findMin(self, nums: List[int]) -&gt; int:        left, right = 0, len(nums) - 1           while left &lt; right:                      # 先加一再除，mid更靠近右边的right              mid = left + (right - left) // 2                       if nums[left] &lt; nums[mid]:                 # 向右移动左边界                        left = mid                                      elif nums[left] &gt; nums[mid]:                    # 向左移动右边界                   right = mid - 1                             return nums[right]</code></pre><h1>回溯</h1><p><strong>回溯通用模板：</strong></p><pre><code class="language-python">result = []def backtrack(路径, 选择列表):    if 满足结束条件:        result.add(路径)        return    for 选择 in 选择列表:        做选择        backtrack(路径, 选择列表)        撤销选择</code></pre><h3 id="a-name-p46-a-LC-46-全排列"><a name="p46"></a> <a href="https://leetcode-cn.com/problems/permutations/" target="_blank" rel="noopener">LC-46 全排列</a></h3><p>给定一个不含重复数字的数组 nums ，返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。<br>示例 1：<br>输入：nums = [1,2,3]<br>输出：[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]</p><pre><code class="language-python">class Solution:    def permute(self, nums: List[int]):        if not nums:            return []        # 最终结果集           res = []        def dfs(remain,path):            if not remain:                res.append(path)                return            size = len(remain)            for _ in range(size):                path.append(remain.pop(0))                dfs(remain,path[:])                remain.append(path.pop())        dfs(nums,[])        return res</code></pre><p>回溯法的终极条件是，全局维护一个可变的状态，所以需要不断的选择和撤销操作。<br>但是如果不存在可变状态，只是作为函数的传参，就不需要撤销操作了，一切都是函数的递归调用。如下</p><pre><code class="language-python">class Solution(object):    # DFS    def permute(self, nums):        res = []        self.dfs(nums, [], res)        return res    def dfs(self, nums, path, res):        if not nums:            res.append(path)            # return # backtracking        for i in range(len(nums)):            self.dfs(nums[:i]+nums[i+1:], path+[nums[i]], res)</code></pre><h3 id="a-name-p78-a-LC-78-子集"><a name="p78"></a> <a href="https://leetcode-cn.com/problems/subsets/submissions/" target="_blank" rel="noopener">LC-78 子集</a></h3><p>给你一个整数数组 nums ，数组中的元素 互不相同 。返回该数组所有可能的子集（幂集）。<br>解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。<br>示例 1：<br>输入：nums = [1,2,3]<br>输出：[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]</p><pre><code class="language-python">class Solution:    def subsets(self, nums: List[int]) -&gt; List[List[int]]:        res = []        def dfs(path, i):            res.append(path)            # 不能重复 用j+1向后遍历 没有回溯 直接是dfs            for j in range(i, len(nums)):                dfs(path+[nums[j]], j+1)                        dfs([], 0)        return res</code></pre><p>清楚版：</p><pre><code class="language-python">class Solution:    def subsets(self, nums: List[int]) -&gt; List[List[int]]:        res = []        def dfs(i, remain, path):            res.append(path)            size = len(remain)            for j in range(i, size):                path.append(nums[j])                # 下一次迭代 j+1                dfs(j+1, remain, path[:])                path.pop()        dfs(0,nums,[])        return res</code></pre><h3 id="a-name-p77-a-LC-77-组合"><a name="p77"></a> <a href="https://leetcode-cn.com/problems/combinations/" target="_blank" rel="noopener">LC-77 组合</a></h3><p>给定两个整数 n 和 k，返回范围 [1, n] 中所有可能的 k 个数的组合。<br>你可以按 任何顺序 返回答案。<br>示例 1：<br>输入：n = 4, k = 2<br>输出：<br>[<br>[2,4],<br>[3,4],<br>[2,3],<br>[1,2],<br>[1,3],<br>[1,4],<br>]</p><pre><code class="language-python">class Solution:    def combine(self, n: int, k: int) -&gt; List[List[int]]:        nums = [i for i in range(1, n+1)]        res = []        def dfs(i, path):            if len(path) == k:                res.append(path)                return                        for j in range(i, len(nums)):                # 压入                path.append(nums[j])                # 迭代，当前index往后一位开始迭代                dfs(j+1, path[:])                # 弹出                path.pop()        dfs(0, [])        return res</code></pre><p>上述三题为所有回溯题都会沿用的模板，<strong>不要去想回溯的过程</strong>，直接调用模板</p><h3 id="a-name-p39-a-LC-39-组合总和"><a name="p39"></a> <a href="https://leetcode-cn.com/problems/combination-sum/" target="_blank" rel="noopener">LC-39 组合总和</a></h3><p>给定一个无重复元素的正整数数组 candidates 和一个正整数 target ，找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。</p><p>输入: candidates = [2,3,6,7], target = 7<br>输出: [[7],[2,2,3]]</p><p>输入: candidates = [2,3,5], target = 8<br>输出: [[2,2,2,2],[2,3,3],[3,5]]</p><p>三要素：</p><ol><li>path 记录走过的节点</li><li>remain 剩余的array，用index索引，或者不断pop remain的数组</li><li>判断结果符合，可以借助排序来避免剪枝</li></ol><pre><code class="language-python">class Solution:    def combinationSum(self, candidates: List[int], target: int) -&gt; List[List[int]]:        res = []        candidates.sort()        if target &lt; min(candidates):            return res        def dfs(start, path, gap):            if gap == 0:                res.append(path)                return             if gap &lt; min(candidates):                return             for i in range(start, len(candidates)):                path.append(candidates[i])                dfs(i, path[:], gap - candidates[i])                path.pop()        dfs(0, [], target)        return res</code></pre><h3 id="a-name-p22-a-LC-22-括号生成"><a name="p22"></a> <a href="https://leetcode-cn.com/problems/generate-parentheses/" target="_blank" rel="noopener">LC-22 括号生成</a></h3><p>数字 n 代表生成括号的对数，请你设计一个函数，用于能够生成所有可能的并且 有效的 括号组合。<br>有效括号组合需满足：左括号必须以正确的顺序闭合。<br>示例 1：<br>输入：n = 3<br>输出：[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]</p><p>DFS + 剪枝，不需要维护一个状态:</p><pre><code class="language-python">class Solution:    def generateParenthesis(self, n: int) -&gt; List[str]:        if n == 0:            return []        res = []        def dfs(cur, left, right):            if left == n and right == n:                res.append(cur)            if left &lt; n:                dfs(cur + &quot;(&quot;, left + 1, right)            # 如果右括号比左括号多，一定不能构成有效括号            if left &gt; right:                dfs(cur + &quot;)&quot;, left, right + 1)        dfs(&quot;&quot;, 0, 0)        return res</code></pre><p>回溯，需要维护一个path状态</p><pre><code class="language-python">class Solution:    def generateParenthesis(self, n: int) -&gt; List[str]:                def backtrace(left, right, path):            if left == n  and right == n:                res.append(''.join(path))                return                         if left &lt; n:                path.append('(')                backtrace(left + 1, right, path[:])                path.pop()                        if left &gt; right:                path.append(&quot;)&quot;)                backtrace(left, right + 1, path[:])                path.pop()                    res = []        backtrace(0, 0, [])        return res</code></pre><h3 id="a-name-p79-a-LC-79-单词搜索"><a name="p79"></a> <a href="https://leetcode-cn.com/problems/word-search/" target="_blank" rel="noopener">LC-79 单词搜索</a></h3><p>给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中，返回 true ；否则，返回 false 。<br>单词必须按照字母顺序，通过相邻的单元格内的字母构成，其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。</p><p>输入：board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”<br>输出：true</p><pre><code class="language-python">class Solution:    def exist(self, board: List[List[str]], word: str) -&gt; bool:        def dfs(i, j, k):            if k == len(word) - 1 and 0&lt;=i&lt;=a-1 and 0&lt;=j&lt;=b-1 and word[k] == board[i][j]:                return True            if not 0&lt;=i&lt;=a-1 or not 0&lt;=j&lt;=b-1 or word[k] != board[i][j]:                return False            board[i][j] = ''                        i1 = dfs(i-1, j, k+1)            i2 = dfs(i+1, j, k+1)            j1 = dfs(i, j-1, k+1)            j2 = dfs(i, j+1, k+1)            # 这步退格很关键            board[i][j] = word[k]            return i1 or i2 or j1 or j2                a, b = len(board), len(board[0])                for i in range(0, a):            for j in range(0, b):                if dfs(i, j, 0):                    return True        return False            </code></pre><h3 id="a-name-p47-a-LC-47-全排列2"><a name="p47"></a> <a href="https://leetcode-cn.com/problems/permutations-ii/" target="_blank" rel="noopener">LC-47 全排列2</a></h3><p>给定一个可包含重复数字的序列 nums ，按任意顺序 返回所有不重复的全排列。<br>示例 1：<br>输入：nums = [1,1,2]<br>输出：<br>[[1,1,2],<br>[1,2,1],<br>[2,1,1]]</p><p>示例 2：<br>输入：nums = [1,2,3]<br>输出：[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]</p><p>回溯，全排列类问题，需要对数组排序<br>新增used array用来记录index是否被访问的状态。状态回退时，不仅要回退path，used也需要回退</p><pre><code class="language-python">class Solution:    def permuteUnique(self, nums: List[int]) -&gt; List[List[int]]:        res = []        used = [0] * len(nums)        # 排序        nums.sort()        if not nums:            return []        def dfs(path):            if len(path) == len(nums):                res.append(path[:])                return                         for i in range(len(nums)):                if used[i] &gt; 0:                    continue                # 同层前一个数相同，且未被用到，则需要跳过                if i &gt; 0 and nums[i] == nums[i-1] and not used[i-1]:                    continue                path.append(nums[i])                used[i] = 1                dfs(path)                # 状态回退                path.pop()                used[i] = 0                dfs([])        return res</code></pre><h3 id="a-name-p40-a-LCR-082-组合总和-II"><a name="p40"></a> <a href="https://leetcode.cn/problems/combination-sum-ii/description/" target="_blank" rel="noopener">LCR 082. 组合总和 II</a></h3><p>给定一个数组 candidates 和一个目标数 target ，找出 candidates 中所有可以使数字和为 target 的组合。<br>candidates 中的每个数字在每个组合中只能使用一次。<br>注意：解集不能包含重复的组合。 <br>示例 1:<br>输入: candidates = [10,1,2,7,6,1,5], target = 8,<br>输出:<br>[<br>[1,1,6],<br>[1,2,5],<br>[1,7],<br>[2,6]<br>]</p><p>排列组合题，先排序；因为同个数字只能用一次，数字相同时，取最后一个。</p><pre><code class="language-python">class Solution:    def combinationSum2(self, candidates: List[int], target: int) -&gt; List[List[int]]:        if not candidates:            return []        candidates.sort()        n = len(candidates)        res = []                def backtrack(i, tmp_sum, tmp_list):            if tmp_sum == target:                res.append(tmp_list)                return             for j in range(i, n):                if tmp_sum + candidates[j]  &gt; target : break                if j &gt; i and candidates[j] == candidates[j-1]:continue                backtrack(j + 1, tmp_sum + candidates[j], tmp_list + [candidates[j]])        backtrack(0, 0, [])            return res</code></pre><h1>DFS/BFS</h1><h3 id="a-name-p200-a-LC-200-岛屿数量"><a name="p200"></a> <a href="https://leetcode-cn.com/problems/number-of-island" target="_blank" rel="noopener">LC-200 岛屿数量</a></h3><p>给你一个由 ‘1’（陆地）和 ‘0’（水）组成的的二维网格，请你计算网格中岛屿的数量。<br>岛屿总是被水包围，并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。<br>此外，你可以假设该网格的四条边均被水包围。<br>示例 1：<br>输入：grid = [<br>[“1”,“1”,“1”,“1”,“0”],<br>[“1”,“1”,“0”,“1”,“0”],<br>[“1”,“1”,“0”,“0”,“0”],<br>[“0”,“0”,“0”,“0”,“0”]<br>]<br>输出：1</p><p>DFS</p><pre><code class="language-python">class Solution:    def numIslands(self, grid: List[List[str]]) -&gt; int:        rows = len(grid)        cols = len(grid[0])        def dfs(grid, i, j):            if i &gt;= rows or i &lt; 0 or j &gt;= cols or j &lt; 0 or grid[i][j] == '0':                return             grid[i][j] = '0'            dfs(grid, i, j+1)            dfs(grid, i, j-1)            dfs(grid, i+1, j)            dfs(grid, i-1, j)        count = 0        for i in range(rows):            for j in range(cols):                if grid[i][j] == '1':                    dfs(grid, i, j)                    count += 1        return count</code></pre><p>BFS 借助队列</p><pre><code class="language-python">class Solution:    def numIslands(self, grid: List[List[str]]) -&gt; int:        #bfs        from collections import deque        def bfs(grid, i, j):            queue = deque([[i, j]])            while queue:                l = queue.popleft()                x, y = l[0], l[1]                if 0 &lt;= x &lt; len(grid) and 0 &lt;= y &lt; len(grid[0]) and grid[x][y] == '1':                    grid[x][y] = '0'                    queue.extend([[x+1, y], [x-1, y], [x, y+1], [x, y-1]])        count = 0        for i in range(len(grid)):            for j in range(len(grid[0])):                if grid[i][j] == '1':                    bfs(grid, i, j)                    count += 1        return count</code></pre><h3 id="a-name-p695-a-LC-695-岛屿的最大面积"><a name="p695"></a> <a href="https://leetcode-cn.com/problems/max-area-of-island/" target="_blank" rel="noopener">LC-695 岛屿的最大面积</a></h3><p>给定一个包含了一些 0 和 1 的非空二维数组 grid 。<br>一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合，这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0（代表水）包围着。<br>找到给定的二维数组中最大的岛屿面积。(如果没有岛屿，则返回面积为 0)<br>示例 1:<br>[[0,0,1,0,0,0,0,1,0,0,0,0,0],<br>[0,0,0,0,0,0,0,1,1,1,0,0,0],<br>[0,1,1,0,1,0,0,0,0,0,0,0,0],<br>[0,1,0,0,1,1,0,0,1,0,1,0,0],<br>[0,1,0,0,1,1,0,0,1,1,1,0,0],<br>[0,0,0,0,0,0,0,0,0,0,1,0,0],<br>[0,0,0,0,0,0,0,1,1,1,0,0,0],<br>[0,0,0,0,0,0,0,1,1,0,0,0,0]]<br>对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ，因为岛屿只能包含水平或垂直的四个方向的 1 。</p><p>BFS, 增加面积的计数</p><pre><code class="language-python">class Solution:    def maxAreaOfIsland(self, grid: List[List[int]]) -&gt; int:        from collections import deque        def bfs(grid, i, j):            queue = deque([[i, j]])            area = 0            while queue:                l = queue.popleft()                x, y = l[0], l[1]                if 0&lt;=x&lt;len(grid) and 0&lt;=y&lt;len(grid[0]) and grid[x][y] == 1:                    grid[x][y] = 0                    area += 1                    queue.extend([[x-1, y], [x+1, y], [x, y-1], [x, y+1]])            return area        max_area = 0        for i in range(len(grid)):            for j in range(len(grid[0])):                if grid[i][j] == 1:                    max_area = max(bfs(grid, i, j), max_area)        return max_area</code></pre><pre><code class="language-python">class Solution(object):    def maxAreaOfIsland(self, grid):        &quot;&quot;&quot;        :type grid: List[List[int]]        :rtype: int        &quot;&quot;&quot;        row = len(grid)        col = len(grid[0])        res = 0        def dfs(i, j):            if i &gt;= 0 and i &lt;= row - 1 and j &gt;= 0 and j &lt;= col - 1 and grid[i][j]:                grid[i][j] = 0                return 1 + dfs(i-1, j) + dfs(i+1, j) + dfs(i, j-1) + dfs(i, j+1)            return 0        for i in range(row):            for j in range(col):                if grid[i][j]:                    res = max(res, dfs(i, j))        return res</code></pre><h3 id="a-name-p1254-a-LC-1254-统计封闭岛屿的数目"><a name="p1254"></a> <a href="https://leetcode-cn.com/problems/number-of-closed-islands/" target="_blank" rel="noopener">LC-1254 统计封闭岛屿的数目</a></h3><p>有一个二维矩阵 grid ，每个位置要么是陆地（记号为 0 ）要么是水域（记号为 1 ）。<br>我们从一块陆地出发，每次可以往上下左右 4 个方向相邻区域走，能走到的所有陆地区域，我们将其称为一座「岛屿」。<br>如果一座岛屿 完全 由水域包围，即陆地边缘上下左右所有相邻区域都是水域，那么我们将其称为 「封闭岛屿」。<br>请返回封闭岛屿的数目。</p><p>输入：grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]<br>输出：2<br>解释：<br>灰色区域的岛屿是封闭岛屿，因为这座岛屿完全被水域包围（即被 1 区域包围）。</p><p>思路：先把边界全部都置为1，再进行整体的dfs，dfs中遇到1就返回</p><pre><code class="language-python">class Solution:    def closedIsland(self, grid: List[List[int]]) -&gt; int:        m, n = len(grid), len(grid[0])        def dfs(x, y):            if grid[x][y] == 1:                return            grid[x][y] = 1            for mx, my in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:                if 0 &lt;= mx &lt; m and 0 &lt;= my &lt; n:                    dfs(mx, my)        for i in range(m):            dfs(i, 0)            dfs(i, n-1)        for j in range(n):            dfs(0, j)            dfs(m-1, j)        ans = 0        for i in range(m):            for j in range(n):                if grid[i][j] == 0:                    dfs(i, j)                    ans += 1        return ans</code></pre><h3 id="a-name-p394-a-LC-394-字符串解码"><a name="p394"></a> <a href="https://leetcode-cn.com/problems/decode-string/" target="_blank" rel="noopener">LC-394 字符串解码</a></h3><p>给定一个经过编码的字符串，返回它解码后的字符串。</p><p>编码规则为: k[encoded_string]，表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。<br>你可以认为输入字符串总是有效的；输入字符串中没有额外的空格，且输入的方括号总是符合格式要求的。<br>此外，你可以认为原始数据不包含数字，所有的数字只表示重复的次数 k ，例如不会出现像 3a 或 2[4]的输入。<br>示例 1：<br>输入：s = “3[a]2[bc]”<br>输出：“aaabcbc”<br>示例 2：<br>输入：s = “3[a2[c]]”<br>输出：“accaccacc”</p><pre><code class="language-python">class Solution:    def decodeString(self, s: str) -&gt; str:        stack, res, multi = [], &quot;&quot;, 0        for c in s:            print(res)            if c == '[':                # 遇到 [ 暂存结果和数字                stack.append([multi, res])                res, multi = &quot;&quot;, 0            elif c == ']':                # 遇到 ] pop 组装                cur_multi, last_res = stack.pop()                res = last_res + cur_multi * res            elif '0' &lt;= c &lt;= '9':                multi = multi * 10 + int(c)                        else:                res += c        return res</code></pre><h3 id="a-name-p207-a-LC-207-课程表"><a name="p207"></a> <a href="https://leetcode-cn.com/problems/course-schedule/" target="_blank" rel="noopener">LC-207 课程表</a></h3><p>你这个学期必须选修 numCourses 门课程，记为 0 到 numCourses - 1 。<br>在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出，其中 prerequisites[i] = [ai, bi] ，表示如果要学习课程 ai 则 必须 先学习课程  bi 。<br>例如，先修课程对 [0, 1] 表示：想要学习课程 0 ，你需要先完成课程 1 。<br>请你判断是否可能完成所有课程的学习？如果可以，返回 true ；否则，返回 false 。</p><p>输入：numCourses = 2, prerequisites = [[1,0]]<br>输出：true<br>解释：总共有 2 门课程。学习课程 1 之前，你需要完成课程 0 。这是可能的。</p><p>拓扑排序BFS步骤（最开始的先入队列）</p><ol><li>新建入度表，没有入度说明没有先修要求；新建邻接矩阵，表示 先修 -&gt; 后修</li><li>入度为 0 的 index 入队列</li><li>出队列，总数 -1，对应后修课程入度 -1，判断是否为0，为0继续入队。</li><li>最终判断是否将所有课程遍历结束</li></ol><pre><code class="language-python">class Solution:    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -&gt; bool:        indegrees = [0] * numCourses        adjacency = [[] for _ in range(numCourses)]        queue = deque()        for cur, pre in prerequisites:            indegrees[cur] += 1            # 学完之后可学的课程            adjacency[pre].append(cur)                    # 放入没有入度的节点        for i in range(len(indegrees)):            if not indegrees[i]:                queue.append(i)        while queue:            pre = queue.popleft()            numCourses -= 1            for cur in adjacency[pre]:                indegrees[cur] -= 1                if not indegrees[cur]:                    queue.append(cur)        return not numCourses</code></pre><p>DFS：</p><pre><code class="language-python">class Solution:    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -&gt; bool:        def dfs(i, adjacency, flags):            if flags[i] == -1: return True            if flags[i] == 1: return False            flags[i] = 1            for j in adjacency[i]:                if not dfs(j, adjacency, flags): return False            flags[i] = -1            return True        adjacency = [[] for _ in range(numCourses)]        flags = [0 for _ in range(numCourses)]        for cur, pre in prerequisites:            adjacency[pre].append(cur)        for i in range(numCourses):            if not dfs(i, adjacency, flags): return False        return True</code></pre><h3 id="a-name-p71-a-LC-71-简化路径"><a name="p71"></a> <a href="https://leetcode.cn/problems/simplify-path" target="_blank" rel="noopener">LC-71 简化路径</a></h3><p>给你一个字符串 path ，表示指向某一文件或目录的 Unix 风格 绝对路径 （以 ‘/’ 开头），请你将其转化为 更加简洁的规范路径。<br>输入：path = “/home//foo/”，输出：“/home/foo”<br>输入：path = “/home/user/Documents/…/Pictures”，输出：“/home/user/Pictures”<br>输入：path = “/…/a/…/b/c/…/d/./”，输出：“/…/b/d”<br>输入：path = “/…/”，输出：“/”</p><p>使用栈</p><pre><code class="language-python">class Solution:    def simplifyPath(self, path: str) -&gt; str:        names = path.split(&quot;/&quot;)        stack = list()        for name in names:            if name == &quot;..&quot;:                if stack:                    stack.pop()            elif name and name != &quot;.&quot;:                stack.append(name)        return &quot;/&quot; + &quot;/&quot;.join(stack)</code></pre><h3 id="a-name-p547-a-LC-547-省份数量"><a name="p547"></a> <a href="https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/" target="_blank" rel="noopener">LC-547 省份数量</a></h3><p>有 n 个城市，其中一些彼此相连，另一些没有相连。如果城市 a 与城市 b 直接相连，且城市 b 与城市 c 直接相连，那么城市 a 与城市 c 间接相连。<br>省份 是一组直接或间接相连的城市，组内不含其他没有相连的城市。<br>给你一个 n x n 的矩阵 isConnected ，其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连，而 isConnected[i][j] = 0 表示二者不直接相连。<br>返回矩阵中 省份 的数量。</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, val=0, left=None, right=None):#         self.val = val#         self.left = left#         self.right = rightclass Solution:    def buildTree(self, inorder: List[int], postorder: List[int]) -&gt; TreeNode:        if len(inorder) == 0:            return None        root = TreeNode(postorder[-1])        mid = inorder.index(postorder[-1])        # left        root.left = self.buildTree(inorder[:mid], postorder[:mid])        # right        root.right = self.buildTree(inorder[mid+1:], postorder[mid:-1])        return root</code></pre><pre><code class="language-python">class UnionFind:    def __init__(self):        self.father = {}        # 额外记录集合的数量        self.num_of_sets = 0        def find(self,x):        root = x                while self.father[root] != None:            root = self.father[root]                while x != root:            original_father = self.father[x]            self.father[x] = root            x = original_father                return root        def merge(self,x,y):        root_x,root_y = self.find(x),self.find(y)                if root_x != root_y:            self.father[root_x] = root_y            # 集合的数量-1            self.num_of_sets -= 1        def add(self,x):        if x not in self.father:            self.father[x] = None            # 集合的数量+1            self.num_of_sets += 1class Solution:    def findCircleNum(self, M: List[List[int]]) -&gt; int:        uf = UnionFind()        for i in range(len(M)):            uf.add(i)            for j in range(i):                if M[i][j]:                    uf.merge(i,j)                return uf.num_of_sets</code></pre><h3 id="a-name-p17-a-LC-17-电话号码的字母组合"><a name="p17"></a> <a href="https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/submissions/" target="_blank" rel="noopener">LC-17 电话号码的字母组合</a></h3><p>给定一个仅包含数字 2-9 的字符串，返回所有它能表示的字母组合。答案可以按 任意顺序 返回。<br>给出数字到字母的映射如下（与电话按键相同）。注意 1 不对应任何字母。</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/original_images/17_telephone_keypad.png" alt=""></p><p>输入：digits = “23”<br>输出：[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]</p><pre><code class="language-python">class Solution:    def letterCombinations(self, digits: str) -&gt; List[str]:        if len(digits) &lt; 1:            return []        size = len(digits)        res = []        dic = {&quot;2&quot;: ['a', 'b', 'c'],            &quot;3&quot;: ['d', 'e', 'f'],            '4': ['g', 'h', 'i'],            '5': ['j', 'k', 'l'],            '6': ['m', 'n', 'o'],            '7': ['p', 'q', 'r', 's'],            '8': ['t', 'u', 'v'],            '9': ['w', 'x', 'y', 'z']        }        def dfs(index, path):            if len(path) == size:                res.append(''.join(path))            else:                dig = digits[index]                for item in dic[dig]:                    dfs(index + 1, path + [item])        dfs(0, [])        return res</code></pre><h1>字符串</h1><h3 id="a-name-p151-a-LC-151-翻转字符串里的单词"><a name="p151"></a> <a href="https://leetcode-cn.com/problems/reverse-words-in-a-string/" target="_blank" rel="noopener">LC-151 翻转字符串里的单词</a></h3><p>给你一个字符串 s ，逐个翻转字符串中的所有 单词 。<br>单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。<br>请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。</p><p>说明：<br>输入字符串 s 可以在前面、后面或者单词间包含多余的空格。<br>翻转后单词间应当仅用一个空格分隔。<br>翻转后的字符串中不应包含额外的空格。</p><p>输入：s = &quot;  hello world  &quot;<br>输出：“world hello”<br>解释：输入字符串可以在前面或者后面包含多余的空格，但是翻转后的字符不能包括。</p><pre><code class="language-python">class Solution:    def reverseWords(self, s: str) -&gt; str:        tmp = []        res = []        for word in s.strip().split(' '):            if len(word.strip()) &gt;= 1:                tmp.append(word.strip())        for i in range(len(tmp)):            res.append(tmp[len(tmp) - i - 1])        return ' '.join(res)</code></pre><p>直接写就好了</p><h3 id="a-name-p165-a-LC-165-比较版本号"><a name="p165"></a> <a href="https://leetcode-cn.com/problems/compare-version-numbers/" target="_blank" rel="noopener">LC-165 比较版本号</a></h3><p>给你两个版本号 version1 和 version2 ，请你比较它们。<br>版本号由一个或多个修订号组成，各修订号由一个 ‘.’ 连接。每个修订号由 多位数字 组成，可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号，下标从 0 开始，最左边的修订号下标为 0 ，下一个修订号下标为 1 ，以此类推。例如，2.5.33 和 0.1 都是有效的版本号。<br>比较版本号时，请按从左到右的顺序依次它们的修订号。比较修订号时，只需比较 忽略任何前导零后的整数值 。也就是说，修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号，则该修订号视为 0 。例如，版本 1.0 小于版本 1.1 ，因为它们下标为 0 的修订号相同，而下标为 1 的修订号分别为 0 和 1 ，0 &lt; 1 。<br>返回规则如下：<br>如果 version1 &gt; version2 返回 1，<br>如果 version1 &lt; version2 返回 -1，<br>除此之外返回 0。</p><p>转int后，pop掉末尾的0</p><pre><code class="language-python">class Solution:    def compareVersion(self, version1: str, version2: str) -&gt; int:        v1 = [int(item) for item in version1.split('.')]        v2 = [int(item) for item in version2.split('.')]        # 弹出0        while len(v1) &gt; 1 and v1[-1] == 0: v1.pop()        while len(v2) &gt; 1 and v2[-1] == 0: v2.pop()        index = 0        while index &lt; min(len(v1), len(v2)):            if v1[index] &gt; v2[index]: return 1            elif v1[index] &lt; v2[index]: return -1            else: index += 1        if len(v1) &gt; len(v2): return 1        elif len(v1) &lt; len(v2): return -1        else: return 0</code></pre><h3 id="a-name-p93-a-LC-93-复原ip地址"><a name="p93"></a> <a href="https://leetcode-cn.com/problems/restore-ip-addresses/" target="_blank" rel="noopener">LC-93 复原ip地址</a></h3><p>给定一个只包含数字的字符串，用以表示一个 IP 地址，返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。<br>有效 IP 地址 正好由四个整数（每个整数位于 0 到 255 之间组成，且不能含有前导 0），整数之间用 ‘.’ 分隔。<br>例如：“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址，但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。<br>示例 1：<br>输入：s = “25525511135”<br>输出：[“255.255.11.135”,“255.255.111.35”]</p><pre><code class="language-python">class Solution:    def restoreIpAddresses(self, s: str) -&gt; List[str]:        if len(s) &lt; 4 or len(s)&gt; 12:            return []        res = []        def dfs(remain, path):            if len(path) == 4 and not remain:                res.append('.'.join(path))                return            for i in range(0, min(3, len(remain)), 1):                ans = remain[0:i+1]                # 大于255 和 先导有0要排除                if int(ans) &gt; 255 or len(str(int(ans))) != len(ans):                    continue                dfs(remain[i+1:], path + [ans])                    dfs(s, [])        return res</code></pre><h3 id="a-name-p227-a-LC-227-基本计算器2"><a name="p227"></a> <a href="https://leetcode-cn.com/problems/restore-ip-addresses/" target="_blank" rel="noopener">LC-227 基本计算器2</a></h3><p>给你一个字符串表达式 s ，请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。<br>示例 1：<br>输入：s = “3+2*2”<br>输出：7</p><pre><code class="language-python">class Solution:    def calculate(self, s: str) -&gt; int:        n = len(s)        stack = []        preSign = '+'        num = 0        for i in range(n):            if s[i] != ' ' and s[i].isdigit():                num = num * 10 + int(s[i])            if i == n - 1 or s[i] in '+-*/':                if preSign == '+':                    stack.append(num)                elif preSign == '-':                    stack.append(-num)                elif preSign == '*':                    stack.append(stack.pop() * num)                else:                    stack.append(int(stack.pop() / num))                preSign = s[i]                num = 0        return sum(stack)</code></pre><h3 id="a-name-p443-a-LC-443-压缩字符串"><a name="p443"></a> <a href="https://leetcode-cn.com/problems/string-compression/" target="_blank" rel="noopener">LC-443 压缩字符串</a></h3><p>给你一个字符数组 chars ，请使用下述算法压缩：<br>从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 ：<br>如果这一组长度为 1 ，则将字符追加到 s 中。否则，需要向 s 追加字符，后跟这一组的长度。</p><p>示例 1：<br>输入：chars = [“a”,“a”,“b”,“b”,“c”,“c”,“c”]<br>输出：返回 6 ，输入数组的前 6 个字符应该是：[“a”,“2”,“b”,“2”,“c”,“3”]<br>解释：<br>“aa” 被 “a2” 替代。“bb” 被 “b2” 替代。“ccc” 被 “c3” 替代。</p><pre><code class="language-python">class Solution:    def compress(self, chars: List[str]) -&gt; int:        ret = 0        head = tail = 0        n = len(chars)        while tail &lt; n:            cnt = 0            # 尾指针向后            while tail &lt; n and chars[tail] == chars[head]:                tail += 1                cnt += 1            chars[ret] = chars[head] # 填写字母            ret += 1            if cnt &gt; 1: # 填写数字                for i in range(len(str(cnt))):                    chars[ret] = list(str(cnt))[i]                    ret += 1            head = tail        return ret </code></pre><h3 id="a-name-p145-a-LC-145-LRU缓存机制"><a name="p145"></a> <a href="https://leetcode-cn.com/problems/lru-cache/" target="_blank" rel="noopener">LC-145 LRU缓存机制</a></h3><p>运用你所掌握的数据结构，设计和实现一个  LRU (最近最少使用) 缓存机制 。<br>实现 LRUCache 类：</p><p>双向链表，哈希表。<br>其中双向链表可以更好的定位到前后的元素，而哈希表是记录所有 key 所在的位置</p><ol><li>get 操作：如果哈希表中有这个 key，则返回这个 key 的值，并且由于是最近使用的，所以需要将其移动至双向链表的头部；没有则返回-1</li><li>put 操作：将（key，value）的一个 pair 加入到双向链表中，这边就存在 2 种情况：如果有则直接修改值，并且移动至链表头部；如果没有值，则新建链表节点，移动至头部，并且需要检查总的长度是否超过 LRU 总的容量，超过则要删除链表中最后一个节点，并且在哈希表里也要弹出这个节点对应的 key。</li></ol><pre><code class="language-python">class DLinkedNode:    def __init__(self, key=0, value=0):        self.key = key        self.value = value        self.prev = None        self.next = Noneclass LRUCache:    def __init__(self, capacity: int):        self.cache = {}        self.capacity = capacity        self.size = 0        self.head = DLinkedNode()        self.tail = DLinkedNode()        self.head.next = self.tail        self.tail.prev = self.head    def addToHead(self, node):        node.next = self.head.next        node.prev = self.head        self.head.next = node        node.next.prev = node    def removeNode(self, node):        node.prev.next = node.next        node.next.prev = node.prev    def moveToHead(self, node):        self.removeNode(node)        self.addToHead(node)    def removeTail(self):        node = self.tail.prev        self.removeNode(node)        return node    def get(self, key: int) -&gt; int:        if key not in self.cache:            return -1        # 如果 key 存在，先通过哈希表定位，再移到头部        node = self.cache[key]        self.moveToHead(node)        return node.value    def put(self, key: int, value: int) -&gt; None:        if key in self.cache:            node = self.cache[key]            node.value = value            # move to the header            self.moveToHead(node)        else:            node = DLinkedNode(key, value)            self.cache[key] = node            # move to the header            self.addToHead(node)            self.size += 1            if self.size &gt; self.capacity:                # remove the tail                removed = self.removeTail()                self.cache.pop(removed.key)                self.size -= 1</code></pre><h3 id="a-name-p54-a-LC-54-螺旋矩阵"><a name="p54"></a> <a href="https://leetcode-cn.com/problems/spiral-matrix/" target="_blank" rel="noopener">LC-54 螺旋矩阵</a></h3><p>给你一个 m 行 n 列的矩阵 matrix ，请按照 顺时针螺旋顺序 ，返回矩阵中的所有元素。<br><img src="https://assets.leetcode.com/uploads/2020/11/13/spiral1.jpg" alt=""></p><pre><code class="language-python">class Solution:    def spiralOrder(self, matrix: List[List[int]]) -&gt; List[int]:        left = top = 0        right = len(matrix[0])        bottom = len(matrix)        ret = []        while left &lt; right and top &lt; bottom:            for i in range(left, right):                ret.append(matrix[top][i])            top += 1            for i in range(top, bottom):                ret.append(matrix[i][right - 1])            right -= 1            if left &lt; right and top &lt; bottom:                for i in range(right - 1, left - 1, -1):                    ret.append(matrix[bottom - 1][i])                bottom -= 1                for i in range(bottom - 1, top - 1, -1):                    ret.append(matrix[i][left])                left += 1        return ret</code></pre><h3 id="a-name-p215-a-LC-215-数组中的第K个最大元素"><a name="p215"></a> <a href="https://leetcode-cn.com/problems/kth-largest-element-in-an-array/" target="_blank" rel="noopener">LC-215 数组中的第K个最大元素</a></h3><p>给定整数数组 nums 和整数 k，请返回数组中第 k 个最大的元素。<br>请注意，你需要找的是数组排序后的第 k 个最大的元素，而不是第 k 个不同的元素。<br>示例 1:<br>输入: [3,2,1,5,6,4] 和 k = 2<br>输出: 5</p><p>直接用python heapq包处理</p><pre><code class="language-python">class Solution:    def findKthLargest(self, nums: List[int], k: int) -&gt; int:        # 直接调用 python 包        maxHeap = []        for x in nums:            heapq.heappush(maxHeap, -x)        for _ in range(1, k, 1):            heapq.heappop(maxHeap)        print(maxHeap)        return -maxHeap[0]</code></pre><pre><code class="language-python">class Solution:    def findKthLargest(self, nums: List[int], k: int) -&gt; int:        heapsize = len(nums)        def heapify(nums, index, heapsize):            max = index            left = 2 * index + 1            right = 2 * index + 2            if left &lt; heapsize and nums[left] &gt; nums[max]:                max = left            if right &lt; heapsize and nums[right] &gt; nums[max]:                max = right            if max != index:                swap(nums, index, max)                # 递归代入新的max节点                heapify(nums, max, heapsize)        def swap(nums, i, j):            tmp = nums[i]            nums[i] = nums[j]            nums[j] = tmp        def buildHeap(nums, heapsize):            for i in range(heapsize // 2 - 1, -1, -1): # 只需要调整有子节点的父节点                heapify(nums, i, heapsize)        buildHeap(nums, heapsize)        for i in range(k-1):            swap(nums, 0, heapsize-1)            heapsize -= 1            heapify(nums, 0, heapsize)        return nums[0]</code></pre><p>快排：</p><pre><code class="language-python">def quick_sort(nums):    if len(nums) &lt;= 1:        return nums    key = nums.pop()    left = []    right = []    for x in nums:        if x &lt; key:            left.append(x)        else:            right.append(x)    return quick_sort(left) + [key] + quick_sort(right)</code></pre><p>原地快排：<br>https://zhuanlan.zhihu.com/p/63227573</p><pre><code class="language-python">def quick_sort(lists,i,j):    if i &gt;= j:        return list    pivot = lists[i]    low = i    high = j    while i &lt; j:        while i &lt; j and lists[j] &gt;= pivot:            j -= 1        lists[i]=lists[j]        while i &lt; j and lists[i] &lt;=pivot:            i += 1        lists[j]=lists[i]    lists[j] = pivot    quick_sort(lists,low,i-1)    quick_sort(lists,i+1,high)    return lists</code></pre><p>归并：</p><pre><code class="language-python">#将两个数组从大到小合并def merge(left, right):    i = 0    j = 0    res = []    while i &lt; len(left) and j &lt; len(right):        if left[i] &lt; right[j]:            res.append(left[i])            i += 1        else:            res.append(right[j])            j += 1    res += left[i:]    res += right[j:]    return resdef merge_sort(nums):    n = len(nums)    if n &lt;= 1:        return nums    mid = n//2    left = merge_sort(nums[:mid])    right = merge_sort(nums[mid:])    return merge(left, right)  </code></pre><p>插入排序:</p><pre><code class="language-python">#从索引1元素a开始,依次和前面比较(从后往前),当该元素a小于已经排序好元素b,那么b向后移一位.最后肯定留一个&quot;坑&quot;,把a塞进去.def insertions_sort(nums):    for i in range(1,len(nums)):        temp = nums[i]        j = i-1        while j &gt;= 0 and  nums[j] &gt; temp:            nums[j+1] = nums[j]            j -= 1        nums[j+1] = temp    return nums</code></pre><p>选择排序:</p><pre><code class="language-python">#遍历数组,每次都把最小的放到相应位置def select_sort(nums):    n = len(nums)    for i in range(n-1):        for j in range(i,n-1):            if nums[i] &gt; nums[j]:                nums[i],nums[j] = nums[j],nums[i]    return nums</code></pre><p>冒泡排序：</p><pre><code class="language-python">#一句话,让大的数沉底#两两比较,让大的数放在数组末端def bubbleSort(nums):    n = len(nums)    f = n    for _ in range(n-1):        for i in range(f-1):            if nums[i] &gt; nums[i+1]:                nums[i],nums[i+1] = nums[i+1],nums[i]        f -= 1    return nums</code></pre>]]></content>
    
    <summary type="html">
    
      &lt;h1&gt;目录&lt;/h1&gt;
&lt;h2 id=&quot;双指针&quot;&gt;双指针&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox0&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox0&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p1&quot;&gt;LC-1 2数求和&lt;/a&gt;&lt;br&gt;
先带上index排序&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox1&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox1&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p15&quot;&gt;LC-15 三数之和&lt;/a&gt;&lt;br&gt;
排序，3指针: i，left=i+1, right=len(nums)-1；i/left/right都需要考虑去重&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox2&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox2&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p16&quot;&gt;LC-16 最接近的三数之和&lt;/a&gt;&lt;br&gt;
同，排序，3指针&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox3&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox3&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p75&quot;&gt;LC-75 颜色分类&lt;/a&gt;&lt;br&gt;
荷兰旗，head,mid=0,tail=n-1, mid=0交换mid和head前进, mid=2交换mid和tail，tail-1, 不然mid+1&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox4&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox4&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p11&quot;&gt;LC-11 盛最多水的容器&lt;/a&gt;&lt;br&gt;
首尾指针，wide=j-i, height=min(h[i], h[j]), max(wide*height)&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox5&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox5&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p763&quot;&gt;LC-763 划分字母区间&lt;/a&gt;&lt;br&gt;
转成字典，key:字母，value:[start, end]的index，合并区间模板&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox6&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox6&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p80&quot;&gt;LC-80 删除排序数组中的重复项&lt;/a&gt;&lt;br&gt;
重复k个的问题。核心：if i &amp;lt; k or nums[i] != nums[length - k]，重新组织数组&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; id=&quot;checkbox7&quot; checked=&quot;true&quot;&gt;&lt;label for=&quot;checkbox7&quot;&gt;&lt;/label&gt;&lt;a href=&quot;#p581&quot;&gt;LC-581 最短无序连续子数组&lt;/a&gt;&lt;br&gt;
分段左、中、右，找到左和右的边界&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="算法" scheme="http://alexjiangzy.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="Leetcode" scheme="http://alexjiangzy.com/tags/Leetcode/"/>
    
  </entry>
  
  <entry>
    <title>Logistics到softmax推导整理</title>
    <link href="http://alexjiangzy.com/2019/02/13/LogisticsSummary/"/>
    <id>http://alexjiangzy.com/2019/02/13/LogisticsSummary/</id>
    <published>2019-02-12T23:49:17.000Z</published>
    <updated>2021-10-23T11:17:42.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近复习了一些基础知识，摘取了一些重要的做了一点推导，也为了之后可能面试会用到。应该是机器学习类的，但是也懒得分那么多类了，就先放在慢学NLP下面吧。这里选择把逻辑回归和softmax归到一起来讲，原因在于更好的比较两者的异同和联系。总结为以下两点：</p><p><strong>逻辑回归的优化目标是极大化对数似然估计，采用梯度上升来学习及更新参数 $\theta$ 向量的值。</strong></p><p><strong>Softmax的优化目标是极小化交叉熵损失函数，采用梯度下降和链式法则来更新各层权值。</strong></p><h1>Logistics Regression</h1><p>先从线性回归开始</p><p>$$h _ { w } \left( x ^ { i } \right) = w _ { 0 } + w _ { 1 } x _ { 1 } + w x _ { 2 } + \ldots + w _ { n } x _ { n }$$</p><p>$$h _ { w } \left( x ^ { j } \right) = w ^ { T } x _ { i } = W ^ { T } X$$</p><p>$$X = \left[ \begin{array} { c } { 1 } \newline { x _ { 1 } } \newline { \dots } \newline { x _ { n } } \end{array} \right] \quad W = \left[ \begin{array} { c } { w _ { 0 } } \newline { w _ { 1 } } \newline { \dots } \newline { w _ { n } } \end{array} \right]$$</p><p>针对线性分类器而言，他解决的是回归问题，为了能更好进行分类问题的探讨，这里就引出了 Logistics 回归。</p><a id="more"></a><h2 id="基础知识">基础知识</h2><p>逻辑回归是假设数据服从 Bernoulli 分布（抛硬币），因此LR属于参数模型。</p><p>其中是对于线性模型，加上了一个 Sigmoid 函数，这个也是神经网络的激活函数，拥有很多良好的特性。1. 拥有很好的激活特性，2. 求导很容易，这一点在 GD 上太重要了，对于 NN 的 BPTT 也起到了极为重要的作用。</p><p>LR 目标函数定义：$h _ { \theta } ( x ) = g \left( \theta ^ { T } x \right)$</p><p>其中 Sigmoid 函数 g(z) 的定义：$g ( z ) = \frac { 1 } { 1 + e ^ { - z } }$</p><p>Sigmoid 函数求导 $g ^ {'} (z)$ 为：</p><p>$$<br>\begin{aligned} g ^ { \prime } ( z ) &amp; = \frac { d } { d z } \frac { 1 } { 1 + e ^ { - z } } \newline &amp; = \frac { 1 } { \left( 1 + e ^ { - z } \right) ^ { 2 } } \left( e ^ { - z } \right) \newline &amp; = \frac { 1 } { \left( 1 + e ^ { - z } \right) ^ { 2 } } \cdot \left( 1 - \frac { 1 } { \left( 1 + e ^ { - z } \right) } \right) \newline &amp; = g ( z ) ( 1 - g ( z ) ) \end{aligned}<br>$$</p><h2 id="似然函数">似然函数</h2><p>假设有n个独立的训练样本 { (x1, y1), (x2, y2), … , (xn, yn)}，y={0, 1}。那每一个观察到的样本 (xi, yi) 出现的概率是：</p><p>$$<br>P \left( \mathrm { y } _ { i } , \mathrm { x } _ { i } \right) = P \left( \mathrm { y } _ { i } = 1 | \mathrm { x } _ { i } \right) ^ { y _ { i } } \left( 1 - P \left( \mathrm { y } _ { i } = 1 | \mathrm { x } _ { i } \right) \right) ^ { \mathrm { 1 } - y _ { j } }<br>$$</p><p>推广到所有样本下，得到整体的似然函数表达，需要将所有的样本似然函数全部相乘。这里的参数 $\theta$ 是我们要估计的参数，似然函数正比于我们的概率函数 $L ( \theta | x ) \propto P ( \operatorname { x | \theta )}$，累成后得到整体的似然函数表达：</p><p>$$<br>L ( \theta ) = \prod P \left( \mathrm { y } _ { i } = 1 | \mathrm { x } _ { i } \right) ^ { y _ { i } } \left( 1 - P \left( \mathrm { y } _ { i } = 1 | \mathrm { x } _ { i } \right) \right) ^ { 1 - y _ { i } }<br>$$</p><p>具体为</p><p>$$<br>\begin{aligned} L ( \theta )  &amp; = p ( \vec { y } | X ; \theta ) \newline &amp; = \prod _ { i = 1 } ^ { m } p \left( y ^ { ( i ) } | x ^ { ( i ) } ; \theta \right) \newline &amp; = \prod _ { i = 1 } ^ { m } \left( h _ { \theta } \left( x ^ { ( i ) } \right) \right) ^ { y ^ { ( i ) } } \left( 1 - h _ { \theta } \left( x ^ { ( i ) } \right) \right) ^ { 1 - y ^ { ( i ) } } \end{aligned}<br>$$</p><p>累乘的形式不利于进行优化分析，这里将似然函数取对数，得到对数似然函数，作为我们的最终优化目标，运用极大似然估计来求得最优的 $\theta$</p><p>$$<br>\begin{aligned} \ell ( \theta ) &amp; = \log L ( \theta ) \newline &amp; = \sum _ { i = 1 } ^ { m } y ^ { ( i ) } \log h \left( x ^ { ( i ) } \right) + \left( 1 - y ^ { ( i ) } \right) \log \left( 1 - h \left( x ^ { ( i ) } \right) \right) \end{aligned}<br>$$</p><h2 id="最优化求解推导">最优化求解推导</h2><p>利用链式法对目标函数则进行求导以求得最优参数。</p><p>$$<br>\frac { \partial } { \theta _ { j } } J ( \theta ) = \frac { \partial J ( \theta ) } { \partial g \left( \theta ^ { T } x \right) } * \frac { \partial g \left( \theta ^ { T } x \right) } { \partial \theta ^ { T } x } * \frac { \partial \theta ^ { T } x } { \partial \theta _ { j } }<br>$$</p><p>分三部分求导：</p><p>第一部分</p><p>$$<br>\frac { \partial J ( \theta ) } { \partial g \left( \theta ^ { T } x \right) } = y * \frac { 1 } { g \left( \theta ^ { T } x \right) } + ( y - 1 ) * \frac { 1 } { 1 - g \left( \theta ^ { T _ { x } } x \right) }<br>$$</p><p>第二部分</p><p>$$<br>\frac { \partial g \left( \theta ^ { T } x \right) } { \partial \theta ^ { T } x } = g \left( \theta ^ { T } x \right) \left( 1 - g \left( \theta ^ { T } x \right) \right)<br>$$</p><p>第三部分</p><p>$$<br>\frac { \partial \theta ^ { T } x } { \theta _ { j } } = \frac { \partial J \left( \theta _ { 1 } x _ { 1 } + \theta _ { 2 } x _ { 2 } + \cdots \theta _ { n } x _ { n } \right) } { \partial \theta _ { j } } = x _ { j }<br>$$</p><p>整理得到最终形式：</p><p>$$<br>\begin{aligned} \frac { \partial } { \partial \theta _ { j } } \ell ( \theta ) &amp; = \left( y \frac { 1 } { g \left( \theta ^ { T } x \right) } - ( 1 - y ) \frac { 1 } { 1 - g \left( \theta ^ { T } x \right) } \right) \frac { \partial } { \partial \theta _ { j } } g \left( \theta ^ { T } x \right) \newline &amp; = \left( y \frac { 1 } { g \left( \theta ^ { T } x \right) } - ( 1 - y ) \frac { 1 } { 1 - g \left( \theta ^ { T } x \right) } \right) g \left( \theta ^ { T } x \right) \left( 1 - g \left( \theta ^ { T } x ) \right) \right) \frac { \partial } { \partial \theta _ { j } } \theta ^ { T } x \newline &amp; = \left( y \left( 1 - g \left( \theta ^ { T } x \right) \right) - ( 1 - y ) g \left( \theta ^ { T } x \right) \right) x _ { j } \newline &amp; = \left( y - h _ { \theta } ( x ) \right) x _ { j } \end{aligned}<br>$$</p><p>因此总的 $\theta$ 更新公式为：</p><p>$$<br>\theta _ { j } : = \theta _ { j } + \alpha \left( y ^ { ( i ) } - h _ { \theta } \left( x ^ { ( i ) } \right) \right) x _ { j } ^ { ( i ) }<br>$$</p><p><strong>速记：</strong> 对 $\theta$ 的梯度更新为输入 $x_i$ * (y真实值 - logistics 的预测计算值）</p><p>整个迭代过程就是最为一般的梯度下降，逻辑回归的似然函数是典型的凸函数，运用凸优化的方式可以保证求得最优解。要考LR的话就可以这么写了吧。</p><h2 id="扩展">扩展</h2><p>（1）正则化</p><p>上述是最基础的逻辑回归的推导形式，但是在实际中，还是要针对模型进行一些正则化，防止参数规模过大产生过拟合的情况，违背了奥卡姆剃刀。这里列出了含正则化表达式的逻辑回归目标函数：</p><p>$$<br>J ( \theta ) = - \frac { 1 } { N } \sum y \log g \left( \theta ^ { T } x \right) + ( 1 - y ) \log \left( 1 - g \left( \theta ^ { T } x \right) \right) + \lambda | w | _ { p }<br>$$</p><p>（2）关于优化方法，上述给出的是最为一般的梯度下降法，其他的凸优化方法还包括有：</p><ul><li>牛顿法</li><li>拟牛顿法</li><li>DFP算法</li><li>BGFS拟牛顿法<br>（详见李航《统计学习方法》附录B）</li></ul><p>（3）LR 和 SVM 的异同点</p><p><strong>相同点：</strong></p><ul><li>LR和SVM都是分类算法。</li><li>如果不考虑核函数，LR和SVM都是线性分类算法，也就是说他们的分类决策面都是线性的。</li><li>LR和SVM都是监督学习算法，为判别模型。</li></ul><p><strong>不同点：</strong></p><ul><li>本质上是其loss function不同。<br>逻辑回归方法基于概率理论，假设样本为1的概率可以用sigmoid函数来表示，然后通过极大似然估计的方法估计出参数的值。支持向量机基于几何间隔最大化原理，认为存在最大几何间隔的分类面为最优分类面。</li><li>支持向量机只考虑局部的边界线附近的点，而逻辑回归考虑全局（远离的点对边界线的确定也起作用，虽然作用会相对小一些）。</li><li>在解决非线性问题时，支持向量机采用核函数的机制，而LR通常不采用核函数的方法。</li><li>SVM的损失函数就自带正则！！！（损失函数中的1/2||w||^2项），这就是为什么SVM是结构风险最小化算法的原因！！！而LR必须另外在损失函数上添加正则项！！！</li><li>SVM不是概率输出，Logistic Regression是概率输出。</li></ul><p><strong>总而言之，一个基于距离！一个基于概率！</strong></p><hr><h1>Softmax Cross-Entropy 推导</h1><p>为什么衡量softmax多分类的损失函数要使用交叉熵来定义，我们选取比较常用的另外的几种错误率计算：</p><ul><li><p>Classification Error（分类错误率）：无法区分分别。</p></li><li><p>Mean Squared Error (均方误差)：主要原因是逻辑回归配合MSE损失函数时，采用梯度下降法进行学习时，会出现模型一开始训练时，学习速率非常慢的情况（MSE损失函数），二次代价函数的不足，误差大的时候反而学的比较慢。<br><a href="https://postimg.cc/rzqnc6Yh" target="_blank" rel="noopener"><img src="https://i.postimg.cc/jq7GHt6r/image.png" alt="image.png"></a></p></li><li><p>Cross Entropy Error Function（交叉熵损失函数）：交叉熵函数是凸函数，求导时更容易找到全局最优解，函数的性质就比较好，因此学的也就更快。<br><a href="https://postimg.cc/Hj7xpKcy" target="_blank" rel="noopener"><img src="https://i.postimg.cc/qB1CGHpj/image.png" alt="image.png"></a></p></li></ul><p>明确了交叉熵的优势后，将最后一层的网络展开，最终的结果展开成 one-hot 的形式，用 softmax 得到的概率值进行交叉熵的计算，带入公式</p><p>$$<br>J = - \sum _ { c = 1 } ^ { M } y _ { c } \log \left( p _ { c } \right)<br>$$</p><p><a href="https://postimg.cc/BLG6rnhn" target="_blank" rel="noopener"><img src="https://i.postimg.cc/mDPHFcCC/image.png" alt="image.png"></a></p><p>$$<br>\frac { \partial J } { \partial w _ { i } } = \frac { \partial J } { \partial p _ { i } } \cdot \frac { \partial p _ { i } } { \partial s c o r e _ { i } } \cdot \frac { \partial s c o r e _ { i } } { \partial w _ { i } }<br>$$</p><p><strong>计算第一项：</strong></p><p>$$<br>\begin{aligned} \frac { \partial J } { \partial p _ { i } } &amp; = \frac { \partial - [ y \log ( p ) + ( 1 - y ) \log ( 1 - p ) ] } { \partial p _ { i } } \newline &amp; = - \frac { \partial y _ { i } \log p _ { i } } { \partial p _ { i } } - \frac { \partial \left( 1 - y _ { i } \right) \log \left( 1 - p _ { i } \right) } { \partial p _ { i } } \newline &amp; = - \frac { y _ { i } } { p _ { i } } - \left[ \left( 1 - y _ { i } \right) \cdot \frac { 1 } { 1 - p _ { i } } \cdot ( - 1 ) \right] \newline &amp; = - \frac { y _ { i } } { p _ { i } } + \frac { 1 - y _ { i } } { 1 - p _ { i } } \newline &amp; = - \frac { y _ { i } } { \sigma \left( y _ { i } \right) } + \frac { 1 - y _ { i } } { 1 - \sigma \left( y _ { i } \right) }  \end{aligned}<br>$$</p><p>计算第二项，稍微有点复杂，第一种k等于i的情况</p><p>$$<br>\begin{aligned} \frac { \partial p _ { i } } { \partial s c o r e _ { i } } &amp; = \frac { \left( e ^ { y _ { i } } \right) ^ { \prime } \cdot \left( \sum _ { i } e ^ { y _ { i } } \right) - e ^ { y _ { i } } \cdot \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { \prime } } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = \frac { e ^ { y _ { i } } \cdot \sum _ { i } e ^ { y _ { i } } - \left( e ^ { y _ { i } } \right) ^ { 2 } } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = \frac { e ^ { y _ { i } } } { \sum _ { j } e ^ { y _ { i } } } - \frac { \left( e ^ { y _ { i } } \right) ^ { 2 } } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = \frac { e ^ { y _ { i } } } { \sum _ { j } e ^ { y _ { i } } } \cdot \left( 1 - \frac { e ^ { y _ { i } } } { \sum _ { j } e ^ { y _ { i } } } \right) \newline &amp; = \sigma \left( y _ { i } \right) \left( 1 - \sigma \left( y _ { i } \right) \right) \end{aligned}<br>$$</p><p>第二种考虑k不等于i的情况</p><p>$$<br>\begin{aligned} \frac { \partial p _ { k } } { \partial s c o r e _ { i } } &amp; = \frac { \left( e ^ { y _ { k } } \right) ^ { \prime } \cdot \left( \sum _ { i } e ^ { y _ { i } } \right) - e ^ { y _ { i } } \cdot \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { \prime } } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = \frac { 0 \cdot \sum _ { i } e ^ { y _ { i } } - \left( e ^ { y _ { i } } \right) \cdot \left( e ^ { y _ { k } } \right) } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = - \frac { e ^ { y _ { i } } \cdot e ^ { y _ { k } } } { \left( \sum _ { j } e ^ { y _ { i } } \right) ^ { 2 } } \newline &amp; = - \frac { e ^ { y _ { i } } } { \sum _ { j } e ^ { y _ { i } } } \cdot \frac { e ^ { y _ { k } } } { \sum _ { j } e ^ { y _ { i } } } \newline &amp; = - \sigma \left( y _ { k } \right) \cdot \sigma \left( y _ { k } \right) \newline &amp; = \sigma \left( y _ { k } \right) \cdot \left( 1 - \sigma \left( y _ { i } \right) \right) \end{aligned}<br>$$</p><p>总和两个式子的结果，我们得到了统一的形式：<br>$$<br>\frac { \partial p _ { k } } { \partial s c o r e _ { i } } = \left{ \begin{array} { l l } { \sigma \left( y _ { i } \right) \left( 1 - \sigma \left( y _ { i } \right) \right) } &amp; { \text { if } k = i } \newline { \sigma \left( y _ { k } \right) \cdot \left( 1 - \sigma \left( y _ { i } \right) \right) } &amp; { \text { if } k \neq i } \end{array} \right.<br>$$</p><p>即<br>$$<br>\frac { \partial p _ { i } } { \partial s c o r e _ { i } } = \sigma \left( y _ { i } \right) \left( 1 - \sigma \left( y _ { i } \right) \right)<br>$$</p><p>第三项就是直接求导<br>$$<br>\frac { \partial s c o r e _ { i } } { \partial w _ { i } } = x _ { i }<br>$$</p><p>综上计算统一的结果为</p><p>$$<br>\begin{aligned} \frac { \partial J } { \partial w _ { i } } &amp; = \frac { \partial J } { \partial p _ { i } } \cdot \frac { \partial p _ { i } } { \partial s c o r e _ { i } } \cdot \frac { \partial s c o r e _ { i } } { \partial w _ { i } } \newline &amp; = \left[ - \frac { y _ { i } } { \sigma \left( y _ { i } \right) } + \frac { 1 - y _ { i } } { 1 - \sigma \left( y _ { i } \right) } \right] \cdot \sigma \left( y _ { i } \right) \left( 1 - \sigma \left( y _ { i } \right) \right) \cdot x _ { i } \newline &amp; = \left[ - \frac { y _ { i } } { \sigma \left( y _ { i } \right) } \cdot \sigma \left( y _ { i } \right) \cdot \left( 1 - \sigma \left( y _ { i } \right) \right) + \frac { 1 - y _ { i } } { 1 - \sigma \left( y _ { i } \right) } \cdot \sigma \left( y _ { i } \right) \cdot \left( 1 - \sigma \left( y _ { i } \right) \right) \right] \cdot x _ { i } \newline &amp; = \left[ - y _ { i } + y _ { i } \cdot \sigma \left( y _ { i } \right) + \sigma \left( y _ { i } \right) - y _ { i } \cdot \sigma \left( y _ { i } \right) \right] \cdot x _ { i } \newline &amp; = \left[ \sigma \left( y _ { i } \right) - y _ { i } \right] \cdot x _ { i } \end{aligned}<br>$$</p><p>因此如果反向传播的是 $i$ 对应于真实的标签为 1，即 $y_i=1$，那么回传的梯度变化就需要减去 <strong>1</strong>, 再乘以 $x_i$。如果对应真实标签为0，那么 $y_i=0$，传递回该位置的 softmax 值乘以 $x_i$。如此一来，我们就得到了全连接层到输出层的梯度变化了，再回传其实也没有那么困难了。</p><h2 id="总结">总结</h2><p>LR 和 Softmax 是一切机器学习的基础，推导完毕后发现，其实两者还是存在很多的共性，尤其从结论来看，都是（真实-预测）* 输入，但是这个结论的得来要归功于 sigmoid 函数和 softmax 函数良好的凸性，梯度随误差增大而增大，以及易于求导三方面因素共同决定的，也佩服于先人的才学，才有了今天的发展。</p><p>参考：</p><p>https://www.jianshu.com/p/dce9f1af7bc9</p><p>https://zhuanlan.zhihu.com/p/35709485</p><p>https://zhuanlan.zhihu.com/p/25723112</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近复习了一些基础知识，摘取了一些重要的做了一点推导，也为了之后可能面试会用到。应该是机器学习类的，但是也懒得分那么多类了，就先放在慢学NLP下面吧。这里选择把逻辑回归和softmax归到一起来讲，原因在于更好的比较两者的异同和联系。总结为以下两点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;逻辑回归的优化目标是极大化对数似然估计，采用梯度上升来学习及更新参数 $\theta$ 向量的值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Softmax的优化目标是极小化交叉熵损失函数，采用梯度下降和链式法则来更新各层权值。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;Logistics Regression&lt;/h1&gt;
&lt;p&gt;先从线性回归开始&lt;/p&gt;
&lt;p&gt;$$h _ { w } \left( x ^ { i } \right) = w _ { 0 } + w _ { 1 } x _ { 1 } + w x _ { 2 } + \ldots + w _ { n } x _ { n }$$&lt;/p&gt;
&lt;p&gt;$$h _ { w } \left( x ^ { j } \right) = w ^ { T } x _ { i } = W ^ { T } X$$&lt;/p&gt;
&lt;p&gt;$$X = \left[ \begin{array} { c } { 1 } \newline { x _ { 1 } } \newline { \dots } \newline { x _ { n } } \end{array} \right] \quad W = \left[ \begin{array} { c } { w _ { 0 } } \newline { w _ { 1 } } \newline { \dots } \newline { w _ { n } } \end{array} \right]$$&lt;/p&gt;
&lt;p&gt;针对线性分类器而言，他解决的是回归问题，为了能更好进行分类问题的探讨，这里就引出了 Logistics 回归。&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="http://alexjiangzy.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="机器学习" scheme="http://alexjiangzy.com/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>回首2018 -- 每天都是夏天的新加坡留学生活</title>
    <link href="http://alexjiangzy.com/2018/12/31/2018%E6%80%BB%E7%BB%93%E5%B1%95%E6%9C%9B/"/>
    <id>http://alexjiangzy.com/2018/12/31/2018总结展望/</id>
    <published>2018-12-30T17:56:46.000Z</published>
    <updated>2019-01-01T05:16:44.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/201822019.jpg" alt=""><br>（文章主要记录2018年在新加坡国立大学读企业商务分析Master的点点滴滴）</p><p>可能是夏天出生的缘故，我向来就不喜欢冷，那种刺骨血液冰冻僵住的感觉，想来真是让我不舒服。加上又是易出汗体质，也就很不喜欢裹棉衣，稍微活动几下，毛线夹着背脊的汗，那滋味着实让我难受。曾经也幻想过，要是能天天过上夏天，大拖鞋大短裤，没事就可以跳水里游泳吃冷饮，应该会是及其惬意吧。老天有眼，2018年，我就在新加坡就得偿所愿的过了整整365天的夏天，曾经一年有春夏秋冬，四季分明，如今就只剩初夏，盛夏，仲夏。反而贱嗖嗖的有点想念冬天的滋味了哈哈哈。</p><p>2018年年初办完离职，来到新加坡国立大学再次成为了一名学生。今年的足迹大多只局限在北纬1度的新加坡（十足的赤道），出没于NUS Utown、ISS school、实习待的IBM CBP，也去过马来西亚（亚庇）和越南（胡志明，芽庄）旅行。</p><p>2018年整体而言，关键词是充实和自我重建。</p><a id="more"></a><hr><h3 id="初尝NUS留学滋味">初尝NUS留学滋味</h3><p>来到NUS，一切都是战战兢兢的。</p><p>担心过很多事情，竞争、语言、课程强度、考试难度等等。毕竟之前选定这个专业，是自己深思熟虑，做了系统的研究得来的。经过这一年，事实证明，这个选择是正确的，但是这个证明的过程还是多少有点曲折，而且看待这个选择的角度也一定程度上决定了你的心态。</p><p>开始的课程，总体来说难度并不大，相比国内教授知识的方式，满黑板公式填鸭，这边的教育延续了西方教育的精髓，引导式教学。没有公式的推导和证明，循循善诱，讲究实际的应用和案例的分析，ISS学院本身也是很推崇实际应用，因而就也多了很多的workshop，很多课几乎是整节课要你讨论来讨论去，有机会在课堂上实践，比如讨论讨论舆情对股市波动的影响啊，或者教你怎么写R语言啊。教授课程的老师，也都是背景相当惊人，走学术路线的学历基本都是哈佛、斯坦福之流，走实践路线的，基本都是首席科学家，还有几个CTO，由此可见国大在教师质量这块的把控还是相当严格的。当我志得意满还觉得资本主义的教法也不过如此的时候（其实真的也就还好），NUS也祭出了一件终极大杀器，就是Assignment（也就是小组作业）！作业不仅体现在它的体量比较大上，NUS的Assignment形式也比较全面，不光是实现某个模型（比如代码，建模之类），还要写report，有些甚至要写paper，还要西装笔挺人模人样地做presentation，有些小组还滋生了很多矛盾，想象一下同时有好几个这样的作业砸到你头上，多少是有点应接不暇的。</p><p>我所在的小组人员相对比较固定，有2个很不错的新加坡同学，总体都很和睦，只是大家做事都比较较真，这也就带来了不少的痛苦，我们似乎永远会把作业做到很heavy的地步，交assignment的高峰期UTown待到半夜3，4点可以说是家常便饭了，现在回想起来，弄到这么累的原因无外乎3点：1. 好胜心，2. 拖延懒，3. 纠结。不到DDL似乎就是没有什么动力去完成，有时候也自视甚高，觉得不能丢脸吧，但是每个作业涵盖一个学科知识点，总是要花时间去深入消化的，有时候图快，反而到后期发现都是问题，不得不推倒重来，慢慢也发现理解也伴着大家投入的时间增多而不断增强，经常需要大家聚到一起讨论，女朋友甚至怀疑说怎么天天都在讨论，以为在做什么了不起的大事情。</p><p>辛苦是真的辛苦，只记得当时写cluster的作业，写到2点多想吐，隔天没怎么吃饭还得了肠胃炎；做data visualization，和组员蜗居3天搞懂tableau（包括各种联动，dashboard），最后一天大家就睡了3小时就去present了；做service，白板写了一面又一面，还是没能想出很好的办法，抓耳挠腮直到困到动不出脑子。另外英文写作这块，不得不承认英语方面巨大的差距，时常我们写好了，新加坡同学还是需要逐字逐句的给我们改，这块也是拉低效率的因素（我知道别人都是英语母语的同学包办报告的，但是我们还是想自己多锻炼一下）。因而，每个report也从我们几个写的雅思作文蜕变为我们自己都读不太懂的雅思阅读了。可喜的是，我没有遇到太多坑队友的情况，大家都是能为他人着想的去做事情的，这一点我很知足，因为不少组出现了能力水平差距过大，或者态度出现问题撕破脸皮，大打出手的情况。另外，印度的同学其实也是真的很拼，也有不少神级的人物，而且人家英语普遍完爆中国学生，因此也不能太轻视。</p><p><img src="/images/2018summary/1.jpeg" alt=""></p><center style="font-size:13px">（摄于UTown Study Room）</center><br><p>这里有几点经验吧，（1）选课很重要，不是技术背景，就不要选太heavy的技术课，毕竟时间紧任务重，不可能让你从零开始建设自己的知识体系，没人会等着你。（2）组员的选择，一定要是靠谱的至少态度端正的人，而不要只迷信他过往牛逼的背景（遇到过之前埃森哲和四大背景的，失望之极，当然也是个人的问题和企业无关）。（3）老师大多都不错，而且不会都可以发邮件询问，都会得到很好的解答，但也有压榨的老师，要学会分辨或者询问往届的经验。</p><p>记录一些上过的课程：<br>商务分析基础（EB5101-Foundations Of Business Analytics）<br>高级分析（EB5103-Advanced Analytics）<br>数据分析（EB5102-Data Analytics）<br>决策与优化（EB5104-Decision Making And Optimization）<br>用户关系管理（EB5203-Customer Relationship Management）<br>医疗健康分析（EB5205-Clinical Health Analytics）<br>信息系统安全（SG4205-Information Systems Security）<br>计算智能人工智能（KE5206-Computational Intelligence I）<br>文本挖掘（KE5205-Text Mining)<br>系统分析（EB5207-Service Analytics）<br>新媒体与情感挖掘（EB5204-New Media And Sentiment Mining）<br>机器学习与文本处理（EB5002-Text Processing using Machine Learning)<br>网站分析（EB5205-Web Analytics）</p><hr><h3 id="危机与自寻出路">危机与自寻出路</h3><p>然而，慢慢的，我却发现这边教授的东西，都太过于强调应用实践了，可能我这个专业商务分析就是比较重应用，而对于数学原理的介绍和推导，几乎可以说是没有，而且教授的内容也过于简单和过时有些也已经不太适用了。让我感受焦虑的另一点是，今年AI发展之迅猛让我也心急于自己没能做出相适应的成果，（国内）市场永远择优功利，作为技术人出身，可能很难说服自己这么跟随下去，尤其是9月份开始校招之后，了解之下国内不管是算法、机器学习还是数据分析，所要考察的基础知识都十分完备，这些门槛不跨过基础不掌握好的话，很难能适应当下的大趋势，欠缺的知识像决堤一样涌过来。加上作业的负担和考试的压力都不小，很难拥有整段复习的时间。那段时间的我，分外的焦躁，日日夜夜是想补基础而不得，瘦了将近10斤，一度怀疑自己这个留学到底是不是正确的，甚至想过休学自学得了，也就是那段时间思考了很多很多，到底我该怎么安排我的学习生活，我要补哪些，我是谁。。本以为NUS的光环可以让我所向披靡，但是却越发的迷茫彷徨。</p><p>人总不能在一个不好的状态中度过，因此，想办法摆脱困境，改善自己的处境，有计划的实施自我重建，做好心理建设，才是重中之重。分析之下，得出几个比较重要的结论：</p><p>1. 秋招赶不上了，还有春招，我有工作经验，走社招问题也不大。差的只是自己的能力，而应该不会差机会，因此补齐足够的能力，找国内工作这块问题是不大的。</p><p>2. 既然学院给不了我想要的，那我就自己学，于是统计机器学习，我会去看李航和西瓜书，网上的视频笔记应有尽有。深度学习看了standford CS224n，也报了一些算法和NLP的课程。</p><p>3. 深度学习方面的实践，我决定尝试去打一些比赛，这样不仅锻炼能力也可以作为含金量高的经历写在简历上。</p><p>4. 自己学院的课程和作业，我尽量去做我强项的事情，比如写代码。也适当降低了考试复习的强度，我对成绩也没有那么高的执念，而且上学期成绩还算OK。</p><p>5. 评估下来感觉，这个专业（企业商务分析）虽然可能给不了我短期可见的提升，但是长远来看，一定是会有帮助的，尤其是在综合能力，大局观，商务分析等方面来看，因此只要是有帮助，我就应该坚持下去。</p><p>6. 最后，我选择相信自己。</p><p>在这样新的策略和心理建设之下，我又开始了我在IBM的实习生活。实习生活也是五味杂陈，我所在的组是一个跨文化多国籍的纯英语环境，起先，大家觉得你一实习生会什么，于是开始使唤我做一些很没有意思的活儿，比如拖拽生成表格，看视频学习之类的，当然这也不责怪任何人，也是一开始大家定位不明确。在我明确表示我实习不应该干这个的（意思大概就是你这样是委屈了我这个人才），我有我自己实习的scope，而且当前的工作模式实在是各种的不专业，我甚至还发了一次火（同学都说你可能是最狂的实习生了）。领导层开始对于我重新定位，甚至因为我实习的scope还专门开会来讨论。自然，协商的结果是好的，我可以做我预先设计的实习内容，也表扬一下IBM拥有的灵活性和大公司风范。慢慢我开始半天干活，半天复习，来回地铁上我也会听算法课，那段时间过得十二分的积极，也只是傻傻的不想让自己意识和思想堕落（正好室友7年感情长跑的戛然而止，使得整个居住气氛也不是很积极），实习也交了不少朋友，实习这块还要继续进行下去，暂时还前途未卜，也就不展开了。</p><p>慢慢的，我开始发现了自己的改观和一点点成绩吧。最为明显的是，英语表达上，一次在IBM开会阐述某个方案的时候，发现自己可以很从容的表达，也能听懂大家的提问了（毕竟东南亚的英语口语，印度啥的，你懂的），和同事的日常交流也顺畅了很多。自己看李航和周志华的书，慢慢可以一点点看懂了，毕竟我也没有那么好的数学底子，在看到推导的时候经常有醍醐灌顶的感觉。另一个就是比赛，打了大概3个比赛，因为喜欢和看好NLP方向，所以一直在关注这个领域，从达观杯600多名，熟悉比赛的流程和套路，到最近的天池拿了37名。虽然还是不值得大吹特吹，但是能在有限的时间里，尤其天池那个比赛夹在考试期间，也是一个人单枪匹马的写，能做到这样的成绩，我已经相当知足了。学院的assignment也应付的更加从容了，跟着新加坡的几个同学学了很多大方向和 high level 的经验。</p><p>整个2018年的后期可以说，学习工作状态血慢慢回上来了一点。</p><hr><h3 id="神山之行🇲🇾">神山之行🇲🇾</h3><p>第一次旅游是在7月初，马来西亚亚庇，5个人计划了5天的行程，从攀登神山，到游览美人鱼岛，神山的艰辛是事先已知的，本想美人鱼岛应该算是放松，但是却同样的艰辛甚至更加艰辛。费用方面，早早订了亚航的机票，来回几百块人民币可以说是白菜到不行了，费用的大头是神山（门票，保险，向导）整个下来一人接近3000RMB不便宜，其他酒店之类都在正常范围，总体费用不高，出去旅游才发现，大手大脚撒钱的永远是中国人，老外们旅游都普遍比较节俭，吃住都很便宜，装备也都是自带，但都是很专业的。</p><p>备齐了所有装备之后，凌晨3点到达神山脚下的小旅馆，休息了不到4个小时便起身去大部队了，当时没什么概念，后来才意识到休息不好真的会很艰难。租用了登山杖，背上了足够的淡水和干粮就启程上山了，起点处的温度较高属于夏天，大家还是身着短袖短裤，一路有说有笑，不觉得特别累。然而神山毕竟有4000多的海拔，行至中午，已经都纷纷换上长衣了，体力也是越来越捉襟见肘。大概爬到下午4点左右，到达海拔3200的休息站，温度也掉到了个位数，狂风大作，大家已经都换上了带羽绒的冲锋衣和棉帽，这时候体力已经有点极限了，爬的越来越慢。就在还剩300米左右要达到3800的大本营的时候，突然间就下起了暴雨，雨势非常的大，加之风大，第一就是山路变得不容易走了（没有台阶，是泥石路），滑倒了两次，第二虽然冲锋衣和登山裤有防水性，但是在风水裹携之下，没多久就打湿了全身，雨水的蒸发带走了身体大量的热量，让我有了一种不好的预感。艰难到了3800的大本营之后，由于也没有热水可以冲洗，简单冲洗之后，果不其然5个人里面3个人发了烧，也包括我，当时昏昏沉沉喝了点药，便一头栽下去睡着了。等醒来后是第二天凌晨3点，发现自己一身的汗，烧也退了不少，可以说是万幸了。商量之下，发烧的同学就不去submit了，当下觉得有点遗憾，但是当晚风雨实在是太大，原定前去登顶的人也都原路返回了，听他们形容是，雨大到感觉自己像是走在瀑布里，遗憾的感觉也退散了一些。</p><p>等第二天中午，雨停了，泡了一杯咖啡坐在住所楼下，那美景是真的醉人，不管怎么说也算对得起自己的病体了。一路上伙伴们也是相互扶持，没有谁想着退缩的，向导也一路帮我们提了大部分行李，人很好。下山相对顺利很多，边下山也边感慨，自己当时竟是这么一阶一阶爬上来的。所以，爬山和做事同理吧，没有多难，但也绝对不容易，要的唯有踏实的坚持。<br><img src="/images/2018summary/2.jpeg" alt=""></p><center style="font-size:13px">（摄于 Mountain Kinabalu 山顶）</center><br><p>辛苦了2天，接下来的海岛之行，大家都认为是一次全身心的放松之旅之时，现实依旧狠狠打脸。因为美人鱼岛是一个离岸的小岛，所以需要坐船前往，30人左右的一个小船，开始也没当回事，想着坐个船有什么大不了，只是它离岸离的稍微有点远足足有1个半小时的船程，我好像似乎也真的没有太坐过船没什么概念。刚登船时还看着两边的红树林，挺新奇的，遇到两个浪，全船的人都很嗨的在嗨叫。但是慢慢就感觉有点不舒服了，那段时间似乎是雨季来临前，风浪都比平时要大一些。能明显感觉到，海浪一个比一个大，由于船体整个比较小，能明显感觉到船被浪抬升到顶端，再狠狠坠下的感觉，坠下的瞬间感觉到一丝的失重，讲真和坐过山车几乎是一样了。可以过山车过个20分钟了不起了，这个船足足开了有1个多小时。我一个人蜷缩在船边，衣服已经被拍打进来的浪花完全打湿，透过舷窗看到的场景，几乎和大白鲨之类的没有两样了，深海的颜色也不是让人讨喜的浅蓝，而是一种暗蓝接近于黑色，着实让人心生恐惧。之前嗨叫的其他乘客，一个个也都吐的一塌糊涂。后来Ed有告诉我，**其实在风浪都船上还是不要嗨叫，甚至不要张嘴，因为船在颠簸很容易咬断舌头。**当时又正好发生了中国游客泰国普吉岛沉船的事故，当时的情景估计和我正在经历的如出一辙吧。终于1个多小时到达了美人鱼岛，看看同行的人，不是吐的横七竖八，就是吓到嘴唇发白，活着真好。</p><p>美人鱼岛，还是一个未经开发的状态，全岛不通电只能靠发电机，所以自然也就没有什么空调有个电扇吹就不错了。不过景色没话说，白色的细沙，清冽的海水（就是贼咸），看着日出日落。和小伙伴们搭吊床，捉小螃蟹，玩飞盘，独享一个人的时光，我自己一个人还借了个皮管和眼镜去浮潜了一下。躺在沙滩上，整个还是很惬意的，就是海岛上垃圾真的有点多，尤其是塑料瓶，也就理解了英女王痛恨塑料制品的原因。小贴士，这个防晒一定一定一定要做足，男生也是，主要是怕晒伤倒不都是矫情，我后背就被晒掉了一层皮。<br><img src="/images/2018summary/3.jpeg" alt=""></p><center style="font-size:13px">（摄于 Mantanani Island 美人鱼岛）</center><br><p>整个旅行，在哥打基纳巴卢也是品尝了很不错的美食，尤其是有名的生肉面和海鲜（石斑，螃蟹，对虾，拉拉）。面对着那盆海鲜，会不自觉的联想起坐船的经历，想必渔民打渔同样也一定会经历这样的风浪吧，也是在以命搏海，所以城市化的进程也不应忘记那些底层的劳动者，记得餐桌上的美食也从来都得来不易。</p><hr><h3 id="与Cici的越南之行🇻🇳">与Cici的越南之行🇻🇳</h3><p>由于今年女朋友去越南交换，便早早制定了10月初出行的计划，计划了3天的行程包括芽庄和胡志明。行程没有计算好时间，办的是落地签，由于要先去芽庄回合，我又是在胡志明入关，于是订了新加坡-胡志明，胡志明-芽庄连续的2趟飞机。但是两趟飞机时间间隔没有计算好，为保险起见就事先多订了一趟稍晚的胡志明-芽庄的班机。果不其然，当天第一程班机大延误，等到了胡志明，我马不停蹄的去办落地签，发现排队等签的人还是不少，没有VISA也就不能坐接下来的飞机。等真正办好了签证，去问工作人员我原计划的飞机还能登机么，人家萌萌的表示，早都飞的不见了。不过因为提前订了备用的机票，当晚还是顺利到达了芽庄，在芽庄见到了我日思夜想的cici小公主，哈哈。</p><p>和女朋友在一起2年了，这也是第一次真正意义上的异国旅行。芽庄坐落在越南东部沿海，长长的海岸线丝毫不亚于澳洲黄金海岸（虽然我也没有去过哈哈），入住airbnb预定的旅馆，能直接面朝大海，听着海浪声入睡（女朋友居然半夜4点还一个人起来看海！），价钱真的是相当之便宜了，我们订了一个三室两厅全装修的房子，一晚上150RMB。休整一晚，第二天一早又出海去浮潜，哈哈，和女朋友手拉手浮潜，拿着面包喂五颜六色的鱼群，还看到了长满刺的海胆，由于没有准备 GoPro 之类，也就没有音像资料就不再多描述播撒狗粮了，总之，甜蜜，哈哈。浮潜完上船后开始下起了雨，气温降的比较多，女朋友冻的有点哆嗦只能靠体温取暖了。熬到了用餐的岸上，被一碗比较原始的热腾腾的青菜肉末汤补足了元气。也是第一次有机会和女朋友一起躺在沙滩上喝喝饮料聊聊天，恍惚间感觉过上了理想的生活。回程之后，陆续吃了点越南的烧烤，伫立在芽庄的沙滩上，海风拂面，看着涨潮的海水一层盖过一层，内心得到了无与伦比的宁静。回程后，打卡了泥浆浴和芽庄大教堂。旅行的最后一天回到了胡志明，手拉手逛了地标的粉教堂，恰逢过几天是女朋友的生日，于是在小西餐馆里点了牛排和一个小蛋糕，也不知道小可爱许了什么愿望，只希望你永远开心。</p><p>在机场送别的时候，小公主还是无一例外的哭红了双眼，眼泪吧嗒吧嗒往下掉。回程的飞机上，旅行的点点滴滴在我脑中回荡，感恩女朋友全程精心的安排，由于有语言的优势也一路都在做着向导（越南整体的英语水平根本不行），也总是一如既往的细致贴心周到温柔，也会闹闹小脾气爱吃醋。这次旅行打点了很多两人的第一次，第一次手拉手浮潜，第一次一起坐飞机（当时担心订不到一起的票，可小姑娘就是执念的要一起坐飞机，想来也是可爱），第一次泡泥浆浴，第一次一起看海等等，这些第一次也都完好的刻画在爱情的日记里，一刻都没有减少，也一刻也不能删减。</p><p>来新加坡整整一年了，女朋友其实很不喜欢异地恋，是很容易没有安全感的人，以前在上海上班的时候没事就会三天两头跑到公司楼下等我下班。这一整年只能依靠微信和视频联系，我也常常要处理自己的很多事情，有时候也想这个瘦弱有点孤僻的小姑娘是怎么度过相思的每个夜晚。我烦闷的时候，总是会发来萌萌的照片来逗我开心，她有着很好的习惯，对美和艺术的事物有独到的见解，很大程度上也感染着我。哦对，还爱做吃的，说以后老了可以开一个像小白桦那样的小饭店。这一年，也慢慢熟悉和摸清了对方的脾气，更加emphasize睡前高质量的共处时间，有点超现实的色彩，毕竟这个共处是穿越空间的。女朋友雅思一次达阵，也让我着实骄傲了一把，有一种家人般的自豪感，哈哈，不管未来会在哪里，我都要陪着你。<br><img src="/images/2018summary/4.jpeg" alt=""></p><center style="font-size:13px">（和Cici在坐快艇）</center><br><blockquote><p>往后余生<br>风雪是你<br>平淡是你<br>清贫也是你<br>荣华是你<br>心底温柔是你<br>目光所致<br>也是你</p></blockquote><hr><h3 id="小结和展望">小结和展望</h3><p>2018年其实发生了很多事情，不管是我还是所处的这个世界，无法一一细数。站在赤道上的这一年，阳光比以往任何时候都要耀眼，雨水也比以往任何时候都要充盈，浇灌下的我，对自我的认识，对理想的行业，对科学与人工智能，对未来的职业规划，对和这个世界如何相处，对爱的经营，也从稚嫩走向了没那么稚嫩。感谢这一年，家人爱人和身边所有朋友的理解包容和支持，You are all the best！<br>2019年，列一下flag吧：</p><ol><li>重视内容输出，尤其是有组织和高质量的输出，要养成记录的习惯，整理经营好自己的技术相关博客。这点尤其重要。</li><li>深度方面的推进，尤其是NLP方向吧，还有机器学习原理的整理和算法的巩固训练。</li><li>能将锻炼和健身作为生活的一部分，和吃饭一样重要，继续控制体重。</li><li>想写和发表一篇高质量的论文，不能是水文。</li><li>希望能找到理想的工作吧，希望可以能真正找到契合的工作，等明年这个时候再来看，毕竟大环境比较寒冬。</li><li>好好爱CiCi。</li></ol><p>以上</p><p>2018.12.30<br>于 University Town of NUS<br><img src="/images/2018summary/5.jpeg" alt=""></p><center style="font-size:13px">（雨后 Clementi 粉红晚霞）</center><br>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/201822019.jpg&quot; alt=&quot;&quot;&gt;&lt;br&gt;
（文章主要记录2018年在新加坡国立大学读企业商务分析Master的点点滴滴）&lt;/p&gt;
&lt;p&gt;可能是夏天出生的缘故，我向来就不喜欢冷，那种刺骨血液冰冻僵住的感觉，想来真是让我不舒服。加上又是易出汗体质，也就很不喜欢裹棉衣，稍微活动几下，毛线夹着背脊的汗，那滋味着实让我难受。曾经也幻想过，要是能天天过上夏天，大拖鞋大短裤，没事就可以跳水里游泳吃冷饮，应该会是及其惬意吧。老天有眼，2018年，我就在新加坡就得偿所愿的过了整整365天的夏天，曾经一年有春夏秋冬，四季分明，如今就只剩初夏，盛夏，仲夏。反而贱嗖嗖的有点想念冬天的滋味了哈哈哈。&lt;/p&gt;
&lt;p&gt;2018年年初办完离职，来到新加坡国立大学再次成为了一名学生。今年的足迹大多只局限在北纬1度的新加坡（十足的赤道），出没于NUS Utown、ISS school、实习待的IBM CBP，也去过马来西亚（亚庇）和越南（胡志明，芽庄）旅行。&lt;/p&gt;
&lt;p&gt;2018年整体而言，关键词是充实和自我重建。&lt;/p&gt;
    
    </summary>
    
      <category term="生活感悟" scheme="http://alexjiangzy.com/categories/%E7%94%9F%E6%B4%BB%E6%84%9F%E6%82%9F/"/>
    
    
      <category term="年终总结" scheme="http://alexjiangzy.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Leetcode刷题记录(置顶)</title>
    <link href="http://alexjiangzy.com/2018/12/30/Leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
    <id>http://alexjiangzy.com/2018/12/30/Leetcode刷题记录/</id>
    <published>2018-12-30T12:12:44.000Z</published>
    <updated>2021-07-24T06:31:10.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Leetcode-70-爬楼梯问题（DP）">Leetcode-70 爬楼梯问题（DP）</h2><blockquote><p>You are climbing a stair case. It takes n steps to reach to the top.<br>Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?<br>Note: Given n will be a positive integer.<br>Input: 2<br>Output: 2<br>Explanation: There are two ways to climb to the top.</p></blockquote><a id="more"></a><p>一道经典的爬楼梯问题，直觉上第一个想到的就是采用递归，也就是要计算爬到第3层楼梯有几种方式，可以从第2层爬1级上来，也可以从第1层爬2级上来，所以爬到第3级有几种方式只需要将到第2层总共的种数，加上到第1层总共的种数就可以了。推广到一般，写出递推公式<br>$stairs(n) = stairs(n-1) + stairs(n-2) $，只需要初始化好退出递归的条件就算写完了。</p><p>方法1，直接采用递归。</p><pre><code class="language-python">class Solution:    def climbStairs(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: int        &quot;&quot;&quot;        if n &lt;= 1:            return 1        if n == 2:            return 2        return self.climbStairs(n - 1) + self.climbStairs(n - 2)</code></pre><p>然而，没有AC (ーー゛)，理由是超时。这就引出了在递归里经常会采用的备忘录法，因为这里面同一个n被重复计算了n次，因此一定程度上影响了性能，比如stairs(5) = stairs(4) + stairs(3), stairs(4) = stairs(3) + stairs(2)，stairs(3)就被计算了2次，因此借助一个字典存储计算过的值，就可以大大减少重复的计算了，就诞生了备忘录形式的递归。</p><p>方法2，备忘录递归</p><pre><code class="language-python">class Solution:    def climbStairs(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: int        &quot;&quot;&quot;        refs = dict() # 建立字典类型备忘录        def rec(n):            # 初始条件写入备忘录            if n &lt;= 1:                refs[n] = 1                return 1            if n == 2:                refs[n] = 2                return 2            # 存在于字典的直接输出            if n in refs:                return refs[n]            else:                refs[n] = rec(n - 1) + rec(n - 2)                return refs[n]        return rec(n)</code></pre><p>这次AC了，︿(￣︶￣)︿</p><p>既然都已经做到备忘录了，那其实和动态规划也就没有什么两样，递归采用自顶向下，动态规划采用自底向上，借助一个数组来加以实现，要计算n阶就往对应数组里插入到n阶。</p><p>方法3，动态规划</p><pre><code class="language-python">class Solution(object):        def climbStairs(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: int        &quot;&quot;&quot;        res = [1, 1, 2] #初始化        if n &gt;=3:            for i in range(3, n + 1):                res.append(res[i - 1] + res[i - 2])        return res[n]</code></pre><p>也是AC的，\（￣︶￣）/</p><p>不难发现，这个递推公式有点像Fibonacci数列，其实就是Fibonacci数列。。。因此也可以借助Fibonacci数列递推的思想直接就可以写出来了。</p><p>方法4，Fibonacci递推</p><pre><code class="language-python">class Solution(object):        def climbStairs(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: int        &quot;&quot;&quot;        if n &lt;= 1:            return 1        if n == 2:            return 2        if n &gt;= 3:            first, second = 1, 2            for i in range(3, n + 1):                third = first + second                first = second                second = third            return second</code></pre><p>比较简单和基础的一道题，以上</p><h2 id="leetcode-242-重排校验">leetcode-242 重排校验</h2><blockquote><p>Given two strings s and t , write a function to determine if t is an anagram of s.<br>Example 1:<br>Input: s = “anagram”, t = “nagaram”<br>Output: true</p></blockquote><p>要满足重排，就一定要含有相同个数的字幕，那么就可以转化成 list of chars，看每一个sort过后的list是否相同就可以了。<br>写一个最简单的排序方法</p><pre><code class="language-python">class Solution:    def isAnagram(self, s, t):        &quot;&quot;&quot;        :type s: str        :type t: str        :rtype: bool        &quot;&quot;&quot;        origin = sorted(list(s))        current = sorted(list(t))        return origin == current</code></pre><p>另外就是可以尝试一下Counter这种计数器的方法。虽然这个方法挺不要脸的，利用Counter直接生成一个hashmap。</p><pre><code class="language-python">from collections import Counterclass Solution:    def isAnagram(self, s, t):        &quot;&quot;&quot;        :type s: str        :type t: str        :rtype: bool        &quot;&quot;&quot;        return Counter(s) == Counter(t)</code></pre><h2 id="leetcode-104-二叉树最大深度">leetcode-104 二叉树最大深度</h2><blockquote><p>Given a binary tree, find its maximum depth.<br>The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.<br>Note: A leaf is a node with no children.<br>Given binary tree [3,9,20,null,null,15,7],return its depth = 3.</p></blockquote><p>直觉上，看例子以为是要用数组来做，但是应该是用树。用DFS的思想借助递归实现，（<strong>重点</strong>： 树的题一般都要用递归来解决），先递归左子树，层层深入下去，注意一点在python中，</p><pre><code class="language-python">max(0, None) = 0 </code></pre><p>也就意味着，对某个节点，如果左节点叶子存在，右节点叶子不存在为None的话，这时max()函数取值也为0。<br>整理得出如下：</p><pre><code class="language-python">class Solution:    def maxDepth(self, root):        &quot;&quot;&quot;        :type root: TreeNode        :rtype: int        &quot;&quot;&quot;        if root is None:            return 0        else:            left_height = self.maxDepth(root.left)            right_height = self.maxDepth(root.right)            return max(left_height, right_height) + 1</code></pre><br><h2 id="leetcode-160-链表交集">leetcode-160 链表交集</h2><blockquote><p>Write a program to find the node at which the intersection of two singly linked lists begins.<br>For example, the following two linked lists:</p></blockquote><pre><code class="language-java">A:          a1 → a2                   ↘                     c1 → c2 → c3                   ↗            B:     b1 → b2 → b3</code></pre><p>begin to intersect at node c1.</p><p>链表题用指针，或者双指针还是挺常用的方法。有看到说用字典来存储的，感觉虽然能解决问题，但是一定程度上破坏了链表的数据结构。分析题目，大概有那么2个思路。</p><ol><li>指针顺序遍历，解决一个问题就是2个链表长度不同，所以第一步要遍历得到2个链表的长度（这块可能空间复杂度开销比较大）。将长链表向前移动至剩余链表长度与短链表一致。</li></ol><pre><code class="language-python"># Definition for singly-linked list.# class ListNode(object):#     def __init__(self, x):#         self.val = x#         self.next = Noneclass Solution(object):    def getIntersectionNode(self, headA, headB):        &quot;&quot;&quot;        :type head1, head1: ListNode        :rtype: ListNode        &quot;&quot;&quot;        if headA == None or headB == None:            return None        len_a, len_b = 0, 0        # 赋值计算长度        p, q = headA, headB        while p:            p = p.next            len_a += 1        while q:            q = q.next            len_b += 1                # 赋值截断到相同长度        p, q = headA, headB        if len_a &gt; len_b:            for i in range(len_a - len_b):                p = p.next        else:            for i in range(len_b - len_a):                q = q.next                while p != q:            p = p.next            q = q.next        return p</code></pre><ol start="2"><li>最大的障碍是2个链表长度不同，所以有一个巧妙的办法来补齐。就是当一个链表先行到达链表尾部时，将Next指针去指向另一个链表的头部，同理另一个链表也同样如此，这样就保证了在O（m+n）内一定能找到结果。举个例子：<br>ListNodeA = 0, 9, 1, 2, 4<br>LIstNodeB = 3, 2, 4<br>Path of A -&gt; B = 0, 9, 1, 2, 4, 3, <strong>2, 4</strong><br>Path of B -&gt; A = 3, 2, 4, 0, 9, 1, <strong>2, 4</strong><br>这样只需要遍历一次，如果遍历结束2个指针仍然不同都指向None，也就没有交集.</li></ol><pre><code class="language-python">class Solution(object):            def getIntersectionNode(self, headA, headB):        &quot;&quot;&quot;        :type head1, head1: ListNode        :rtype: ListNode        &quot;&quot;&quot;        p, q = headA, headB                while p != q:            p = headB if p is None else p.next            q = headA if q is None else q.next        return p</code></pre><h2 id="Leetcode-204-质数个数">Leetcode-204 质数个数</h2><blockquote><p>Count the number of prime numbers less than a non-negative number, n.<br>Example:<br>Input: 10<br>Output: 4<br>Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.</p></blockquote><p>乍一看以为应该没什么的题目，结果写了一版，先是边界条件也写好，再就是TLE感觉过不去了。先来一个基本方法吧，毕竟我不可能在实战中短时间想到一个fancy的方法。</p><pre><code class="language-python">import mathclass Solution:    def countPrimes(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: int        &quot;&quot;&quot;        import math        count = 0        # 一个判断是否为质数的方法        def judge_prime(w):            sqrt_w = int(math.sqrt(w))            # 迭代相除直到sqrt(w)            # 注意传入2时，sqrt(2) + 1 = 2， range(2, 2)不会执行，因此还是会返回1            # 不然边界条件太乱了。            for i in range(2, sqrt_w + 1):                if x % i == 0:                    return 0                            return 1        for x in range(2, n):            count = count + judge_prime(x)        return count</code></pre><p>但是这个方法没有在Leetcode上AC，主要还是太慢了。</p><p>看了网上的大神介绍了一个厄拉多塞筛法(Sieve of Eeatosthese)。先上代码，</p><pre><code class="language-python">def countPrimes(self, n):    if n &lt; 3:        return 0    primes = [True] * n    primes[0] = primes[1] = False    for i in range(2, int(n ** 0.5) + 1):        if primes[i]:            primes[i * i: n: i] = [False] * len(primes[i * i: n: i])    return sum(primes)</code></pre><p>对着代码解释下大概的思想是：</p><ol><li>创建一个数组，长度为n个True，第0，1个位置设置为False，即0和1不是质数。</li><li>从2开始（True，初始化），用2举例，以2*2作为起点开始迭代，迭代的终点是n，步长为2，将满足的都标记为False。</li><li>同理3，4由于已经被2标记了跳过，5同理，6被标记，7同理，循环的终点就是$\sqrt{n}$。这里解释一下，因为一共有n个数，每次的起点是i*i，这是因为避免了重复的计算，比如3是从3*3开始的，因为3*2已经计算过了，$\sqrt{n} * \sqrt{n}$作为终点也是保证了不重复计算。总的遍历从2到$\sqrt{n}$，次数下降了指数级别。</li><li>这里又借助了python list的可以设置变步长的性质 [i*i : n : i]，也不需要额外开辟更大的空间，可以说是时间复杂度和空间复杂度都做到了极致。</li><li>AC了，但是凭空我肯定是想不到的。</li></ol><h2 id="Leetcode-17-Letter-Combinations-of-a-Phone-Number-字母组合">Leetcode-17 Letter Combinations of a Phone Number 字母组合</h2><blockquote><p>Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.<br>A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.<br>其中按键对应的字母映射要自己建一个dictionary，汗。。。如下：<br>(letters = {‘2’: ‘abc’, ‘3’: ‘def’, ‘4’: ‘ghi’, ‘5’: ‘jkl’,<br>‘6’: ‘mno’, ‘7’: ‘pqrs’, ‘8’: ‘tuv’, ‘9’: ‘wxyz’})<br>Input: “23”<br>Output: [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].</p></blockquote><p>这个题乍一看，典型的递归，深度遍历的长相，于是乎，开始写了起来，毕竟是递归嘛，脑子大概想着要有一个重复子问题。也差不多想到了就是每一次迭代都要拿上一次的结果，拼接上新一次的字母，而且这个拼接要是一个组合，所以总体来说规模是要逐步扩大的。因此，既然要扩大，应该是在for循环里套一层递归的大概样子。写了一个大概的版本，尽然AC了。。。猝不及防</p><pre><code class="language-python">class Solution:    def letterCombinations(self, digits):        &quot;&quot;&quot;        :type digits: str        :rtype: List[str]        &quot;&quot;&quot;        letters = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',                    '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}                def dfs(res, index):                new_lts = [i for i in letters[input[index]]]            res = [i + j for i in res for j in new_lts] # 拼接生成最新的结果            index += 1            # 递归结束标志            if index &gt; len(input) - 1:                return res            else:                return dfs(res, index) 每次传入当前结果，以及下一个新的数字键                        input = list(digits)        如果输入为空，则返回空。        if len(input) &lt; 1:            return []        # 初始化从1开始        initial = [i for i in letters[input[0]]]        idx = 1        if len(input) == 1:            return initial                return dfs(initial, idx)</code></pre><p>但是这个递归是写的有点问题的，其实似乎就有点不像个递归了。原因在于因为递归是把复杂问题化小，递归到越来越简单的规模。而我这里只是借助了一个递归来传递我每一次新的值给下一个数字键，既然是传递，那其实可以直接写for循环来实现的，于是我改了改：</p><pre><code class="language-python">class Solution:    def letterCombinations(self, digits):        &quot;&quot;&quot;        :type digits: str        :rtype: List[str]        &quot;&quot;&quot;        letters = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',                    '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}                input = list(digits)        if len(input) &lt; 1:            return []                comb = [i for i in letters[input[0]]]        for num in input[1:]:            comb = [i + j for i in comb for j in letters[num]]        return comb</code></pre><p>这样改写之后，就比我之前的第一种顺眼多了。这里的技巧就是循环重复更改结果，第一种里的res和第二种里的comb。<br>而真正使用递归来求解，我参照了一下大神的标准答案，很巧妙运用了化繁为简的策略，这里运用了一个小技巧就是使用了list的[:-1]来递归前一种情况。也就是说本来比如输入5个数字，那递归到第5次得到的结果就是前4次加上第5次的，那第4次就是前3次加第4次的，以此类推。递推公式为<br>$$comb(n) = F(\ comb(n-1),\ curr(n)\ )$$<br>其中$F()$函数就是求组合数，这样想了一下就可以写了如下：</p><pre><code class="language-python">class Solution:    # @param {string} digits    # @return {string[]}    def letterCombinations(self, digits):        mapping = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',                    '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}        if len(digits) == 0:            return []        if len(digits) == 1:            return list(mapping[digits[0]])        prev = self.letterCombinations(digits[:-1])        # additional就是current        additional = mapping[digits[-1]]        return [s + c for s in prev for c in additional] #生成新的组合</code></pre><h2 id="Leetcode-455-Assign-Cookies-分配饼干">Leetcode-455 Assign Cookies 分配饼干</h2><blockquote><p>Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie. Each child i has a greed factor gi, which is the minimum size of a cookie that the child will be content with; and each cookie j has a size sj. If sj &gt;= gi, we can assign the cookie j to the child i, and the child i will be content. Your goal is to maximize the number of your content children and output the maximum number.</p></blockquote><blockquote><p>Note:<br>You may assume the greed factor is always positive.<br>You cannot assign more than one cookie to one child.</p></blockquote><blockquote><p>Example 2:<br>Input: [1,2], [1,2,3]<br>Output: 2<br>Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.<br>You have 3 cookies and their sizes are big enough to gratify all of the children,<br>You need to output 2.</p></blockquote><p>直觉上，这题也太tm简单了吧，拿每个小孩出来比一下，存在就计数加1，饼干顺势干掉1块，循环完然后输出结果，但是报错，发现自己忽略了一个重要的条件，要最多分配给别的小孩。好，那我就饼干选小孩吧，多一个差值位来记录饼干和小孩要求的差距，如果等于0立马输出，如果循环结束都没有等于0，那就选择给差值最小的那个小孩，可能比较丑陋，不过肯定是可以实现的，就写了下来：</p><pre><code class="language-python">class Solution:    def findContentChildren(self, g, s):        &quot;&quot;&quot;        :type g: List[int]        :type s: List[int]        :rtype: int        &quot;&quot;&quot;        #先检查边界        if len(g) == 0 or len(s) == 0:            return 0        count = 0        for ck in s:            tmp = ck            for px in g:                if ck &gt;= px and (ck - px &lt;= tmp):                    tmp = ck - px                    if tmp == 0: #等于0，立刻跳出开始下一个                        count += 1                        g.remove(px)                        break            if tmp &lt; ck and tmp != 0:                g.remove(ck - tmp)                count += 1        return count</code></pre><p>但是，很显然这个方法时间复杂度$O(n^2)$是一定会TLE的。于是想下一个方法，很自然的就是需要对原始数组进行排序，这样就不用存储中间结果了，可以顺序的往下执行。</p><pre><code class="language-python">class Solution:    def findContentChildren(self, g, s):        &quot;&quot;&quot;        :type g: List[int]        :type s: List[int]        :rtype: int        &quot;&quot;&quot;        #先检查边界        if len(g) == 0 or len(s) == 0:            return 0        count = 0        g.sort()        s.sort()        for ck in s:            for px in g:                if ck &gt;= px:                    count += 1                    g.remove(px)                break        return count</code></pre><p>AC了，也可以借助双指针，主指针是饼干，从动的那个指针是小孩，这样就可以省去异常值也放进去循环了，差不多的思想，排序之后其实优化方法还有很多，感觉排序就是一个能让天空放晴的办法，借助while和指针，大概写了一下：</p><pre><code class="language-python">class Solution:    def findContentChildren(self, g, s):        &quot;&quot;&quot;        :type g: List[int]        :type s: List[int]        :rtype: int        &quot;&quot;&quot;        #先检查边界        if len(g) == 0 or len(s) == 0:            return 0                g.sort()        s.sort()        ckp, chp = 0, 0                while ckp &lt; len(s) and chp &lt; len(g):            if s[ckp] &gt;= g[chp]:                chp += 1            ckp += 1                return chp</code></pre><p>巧妙。</p><h2 id="Leetcode-416-Partition-Equal-Subset-Sum-分割相等子集">Leetcode-416 Partition Equal Subset Sum 分割相等子集</h2><blockquote><p>Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.</p></blockquote><blockquote><p>Note:<br>Each of the array element will not exceed 100.<br>The array size will not exceed 200.<br>Example 1:<br>Input: [1, 5, 11, 5]<br>Output: true<br>Explanation: The array can be partitioned as [1, 5, 5] and [11].</p></blockquote><p>经典题，说实话写这个题花了不少时间，尤其是想清楚这个里外里的道理。这道题是一道典型的背包问题，但是在成为背包问题之前，要做一些必要的转换。</p><ul><li>首先，题目要求2个子数组和相等，因此，只有大数组的和为偶数，才可能使得2个子数组和是相等，所以一旦和为奇数就可以立即排除输出<code>False</code>。</li><li>其次对于和为偶数的情况，可以将总和除2作为每个子数组的目标和，所以问题就转化为找出子数组和为$target = sum / 2$。</li><li>DP问题就是要借助DP的思想，就要写一下状态转移方程，作为写代码的指导思想。动态规划都是借助一个2维或者1维的表格来建立整个过程，于是我们借一个例子来画一下表格：</li></ul><pre><code class="language-python">0123456891011010000000000111000000000511000110000511000110001</code></pre><p>行表示每一个数，列表示能够达到所有可能的和（这里可以记忆一下，DP问题是由繁化简，所以一定是有状态从最起始开始一直到我们的目标，在例子中起始为0，目标是11，中间所有可能的值也都要考虑，这点和背包问题是一样的）。由于我们现在的问题是数组和能否达到某个固定的数，因此可以写得递推公式为$dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]$。</p><p>稍微解释一下这个公式，第一项$dp[i-1][j]$是说，如果上一行（i-1）已经满足和为j了，那下一行也一定会满足和为j，所以i项不加也可以满足，状态照搬下来就可以，就好比01背包问题，不取当前项，最大价值仍然延续上一个状态一个意思。而$dp[i-1][j-nums[i]]$表示前i-1个数，在target - nums[i<br>]处已经满足，那加上nums[i]也同样会满足，显示了DP的递推性。这样主体就搭建好了。</p><p>在这里也要注意一下初始值的问题，j为0意味着和为0，也就是不取任何数，所以dp[:][j=0]要设置为True（一个都不取，什么都不用做就满足了，也是强）。于是就可以写下代码了：</p><pre><code class="language-python">class Solution(object):    def canPartition(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: bool        &quot;&quot;&quot;        if sum(nums) % 2 == 1:            return False        target = sum(nums) // 2                # dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]        dp = [[False for i in range(target + 1)] for j in range(len(nums) + 1)]        dp[0][0] = True        for i in range(1, len(nums) + 1):            for j in range(1, target + 1):                dp[i][j] = dp[i-1][j]                if j &gt;= nums[i-1] and dp[i-1][j-nums[i-1]]:                    dp[i][j]  = True        return dp[-1][-1]</code></pre><p>这里有可以改进的地方，就是原始是采用了一个二维数组来存储数据的，其实可以优化为一维数组的，这一点和背包问题是一致的，需要注意的是内层的遍历要降序遍历，来防止对已经生成的数据做重复修改，不展开了，可以参考<a href="https://blog.csdn.net/sunshine_lyn/article/details/79482477" target="_blank" rel="noopener">背包问题整理（二维转一维数组)</a>。改写如下：</p><pre><code class="language-python">class Solution(object):    def canPartition(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: bool        &quot;&quot;&quot;        if sum(nums) % 2 == 1:            return False        target = sum(nums) // 2                # dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]        dp = [False for i in range(target + 1)]        dp[0] = True        for i in range(0, len(nums)):            for j in range(target, nums[i] - 1, -1):                dp[j] = dp[j] | dp[j - nums[i]]        return dp[-1]                </code></pre><p>这题有用别的奇技淫巧来解的，我就不推荐了，主要还是可以借机会复习01背包动态规划的思想，以及转移状态的确定和思维的转换。其他大神的办法无非是在DFS上做文章，我是真的想不到，就老老实实的吧，毕竟慢学。</p><h2 id="Leetcode-215-第K个最大的数">Leetcode-215 第K个最大的数</h2><blockquote><p>Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.</p></blockquote><blockquote><p>Example 1:<br>Input: [3,2,1,5,6,4] and k = 2<br>Output: 5</p></blockquote><p>这道题聊解题没有任何意义，主要是周末想借这个机会复习一下所有的排序算法思想，先给个最简单的题解吧。</p><pre><code class="language-python">class Solution:    def findKthLargest(self, nums, k):        &quot;&quot;&quot;        :type nums: List[int]        :type k: int        :rtype: int        &quot;&quot;&quot;        nums.sort(reverse=True)        return nums[k - 1]</code></pre><p>这里顺便复习几个比较常用的排序算法：冒泡，选择，插入，归并，快排。<br>冒泡：</p><pre><code class="language-python">class Solution:    def findKthLargest(self, nums, k):        for i in range(len(nums)):            for j in range(i + 1, len(nums)):                if nums[j] &gt; nums[i]:                    nums[i], nums[j] = nums[j], nums[i]        return nums[k-1]</code></pre><p>选择：</p><pre><code class="language-python">class Solution:    def findKthLargest(self, nums, k):        for index in range(len(nums)):            tmp = index            for j in range(index + 1, len(nums)):                if nums[j] &gt; nums[tmp]:                    tmp = j            nums[index], nums[tmp] = nums[tmp], nums[index]        return nums[k - 1]</code></pre><p>插入，这个还是想了一下的，主要是用while的话更加清楚一些。</p><pre><code class="language-python">class Solution:    def findKthLargest(self, nums, k):        for i in range(1, len(nums)):            key = nums[i]            j = i - 1            while j &gt;= 0 and nums[j] &lt; key:                # 只要key比已排序好的数大，先对原数进行移位操作，腾出空间。                nums[j + 1] = nums[j] #会有个重复                j -= 1            # while条件不满足时，这里nums[j + 1]其实是(j - 1) + 1 = j，将key置于上步腾出的那个位置            nums[j + 1] = key        return nums[k-1]</code></pre><p>归并：</p><pre><code class="language-python">class Solution:    # 2. 再对left和right作归并排序    def merge_sort(self, left, right):        i, j = 0, 0         result = []        while i &lt; len(left) and j &lt; len(right):            if left[i] &lt;= right[j]:                result.append(left[i])                i += 1            else:                result.append(right[j])                j += 1        result += left[i:]        result += right[j:]        return result    # 1. 先拆分，运用递归    def divide_merge(self, nums):        if len(nums) &lt;= 1:            return nums        num = len(nums) // 2        left = self.divide_merge(nums[:num])        right = self.divide_merge(nums[num:])        return self.merge_sort(left, right)        def findKthLargest(self, nums, k):        res = self.divide_merge(nums)          return res[-k]</code></pre><p>快排：</p><pre><code class="language-python">class Solution:def findKthLargest(self, nums, k):    pivot = nums[0]    left  = [l for l in nums if l &lt; pivot]    equal = [e for e in nums if e == pivot]    right = [r for r in nums if r &gt; pivot]    if k &lt;= len(right):        return self.findKthLargest(right, k)    elif (k - len(right)) &lt;= len(equal):        return equal[0]    else:        return self.findKthLargest(left, k - len(right) - len(equal))</code></pre><p>这里面比较难想的是插入，归并和快排，有必要做一个专题来攻克一下。虽然以前也学过，而且说来说去也大概知道原理，但是真正自己实现的时候，还是没有那么快能解决。</p><h2 id="Leetcode-46-Permutations-排列">Leetcode-46 Permutations 排列</h2><blockquote><p>Given a collection of distinct integers, return all possible permutations.<br>Example:<br>Input: [1,2,3]<br>Output:<br>[<br>[1,2,3],<br>[1,3,2],<br>[2,1,3],<br>[2,3,1],<br>[3,1,2],<br>[3,2,1]<br>]</p></blockquote><p>一道经典的回溯题，看到这样的例子，第一反应就是用DFS搭配回溯，既然是深度优先遍历，一般会用一个visited来记录遍历到的位置，运用for加递归的方式进行遍历，设置退出的条件。回溯算法需要将visited的状态回退，这里运用了一个pop方法将栈中的元素末尾弹出后回溯到上一层状态。这里因为是for循环加上递归的结构，递归其实可以看成是一个一般的函数调用（有返回值），当次的for循环结束后，弹出末尾元素，下一次循环后再压入，以此往复。</p><pre><code class="language-python">class Solution(object):    def permute(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: List[List[int]]        &quot;&quot;&quot;        res = []        def helper(nums, visited):            if len(visited) == len(nums):                res.append(visited[:])                        for item in nums:                # 如果已经存在就不再记入                if item in visited:                    continue                visited.append(item)                helper(nums, visited)                visited.pop()        helper(nums, [])        return res</code></pre><p>AC了，看到另外的解法，避免了在循环中进行判断，而是在传入下层递归的时候，就只传入除去当次循环的那个元素后的剩余元素，要巧妙的多。</p><pre><code class="language-python">class Solution(object):    # DFS    def permute(self, nums):        res = []        self.dfs(nums, [], res)        return res    def dfs(self, nums, path, res):        if not nums:            res.append(path)            # return # backtracking        for i in range(len(nums)):            self.dfs(nums[:i]+nums[i+1:], path+[nums[i]], res)</code></pre><h2 id="Leetcode-647-Palindromic-Substrings-回文子串个数">Leetcode-647 Palindromic Substrings 回文子串个数</h2><blockquote><p>Given a string, your task is to count how many palindromic substrings in this string.<br>The substrings with different start indexes or end indexes are counted as different substrings even they consist of same characters.</p></blockquote><blockquote><p>Example 1:<br>Input: “abc”<br>Output: 3<br>Explanation: Three palindromic strings: “a”, “b”, “c”.<br>Example 2:<br>Input: “aaa”<br>Output: 6<br>Explanation: Six palindromic strings: “a”, “a”, “a”, “aa”, “aa”, “aaa”.</p></blockquote><p>经典题，求回文子串个数，应该只要循环加递归就可以搞定了，理一下思路：回文的特征就是以某一个中心位置为轴对称，但是这里有一点要注意就是奇数个子串有中心位置，但是偶数个数的子串中心位置应该是两个数。明确了这一点之后，循环到某一个位置时，设置两个指针i和j分别从中心位置++和–，直到任何一个指针到达边界小于0或超过数组长度截止，如果有满足 s[i] == s[j] 就继续迭代进行。思路不难，实现了一下：</p><pre><code class="language-python">class Solution:    def __init__(self):        self.count = 0            def countSubstrings(self, s):        &quot;&quot;&quot;        :type s: str        :rtype: int        &quot;&quot;&quot;        def move_check(input, i, j):            if i &lt; 0 or j &gt;= len(input):                return                                if input[i] == input[j]:                self.count += 1            else: # 不满足及时跳出，有效减少无效递归。                return                            i -= 1            j += 1            move_check(input, i, j)                    index = 0        while index &lt; len(list(s)):            move_check(list(s), index, index) #奇数            move_check(list(s), index - 1, index) #偶数            index += 1        return self.count</code></pre><p>AC了，看了个大神的方法，用了for嵌套while的结构，看着还是挺清楚的，我承认我之前非要写个递归，其实就是想自己练练写递归，也没什么特别的道理哈哈。这里用了一个技巧来一步实现奇数偶数的判断，left = center / 2, right = center / 2 + center % 2. 实现如下：</p><pre><code class="language-python">class Solution:    def countSubstrings(self, S):        N = len(S)        ans = 0        for center in range(2*N - 1):            left = center // 2            right = left + center % 2            while left &gt;= 0 and right &lt; N and S[left] == S[right]:                ans += 1                left -= 1                right += 1        return ans</code></pre><h2 id="Leetcode-230-Kth-Smallest-Element-in-a-BST-BST树中第k个最小值">Leetcode-230 Kth Smallest Element in a BST BST树中第k个最小值</h2><blockquote><p>Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.<br>Note:<br>You may assume k is always valid, 1 ≤ k ≤ BST’s total elements.</p></blockquote><blockquote><p>Example 1:<br>Input: root = [3,1,4,null,2], k = 1. Output: 1<br>3<br>/ <br>1   4<br><br>2</p></blockquote><p>最近翻看了很多树类的题目，凡是见到树的题目，脑子就要有一个递归的大体框架，如果递归比较弱的，比如我自己，就可以有针对性的练几个树的题目提高一下。<br>这一题，乍一看也挺简单的，直觉上利用BST树的基本性质，左小右大，于是写了一个遍历左子树的递归，但是报错，这才发现如果K值过大的话，还是需要右子树的，会觉得需要对K做分情况考虑了一下子好像觉得有点复杂了。而像这种第几个XXX的题目，排序似乎永远都是一个不错的方案，全局排序会导致较大的时间开销，设置条件提前停止是一个比较不错的优化方案。</p><p>由于每一棵树都是左&gt;根&gt;右，因此采用中序遍历（LDR）代码如下：</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, x):#         self.val = x#         self.left = None#         self.right = Noneclass Solution:    def kthSmallest(self, root, k):        &quot;&quot;&quot;        :type root: TreeNode        :type k: int        :rtype: int        &quot;&quot;&quot;        vals = [] # 初始化一个数组        def ldr(node):            if node is None:                return             dfs(node.left)            vals.append(node.val)            dfs(node.right)        ldr(root)        return vals[k - 1]        </code></pre><p>为了训练，也可以用非递归的形式来解，但是这边需要，我这里练习并记录一下</p><pre><code class="language-python">class Solution:    def kthSmallest(self, root, k):        &quot;&quot;&quot;        :type root: TreeNode        :type k: int        :rtype: int        &quot;&quot;&quot;        stack = []  # stack以栈来放置node，到达放入，结束后弹出        node = root # node用来表示当前的节点        res = []    # 存放排序结果        while node or stack:            while node:                stack.append(node) # 插入当前节点                node = node.left            node = stack.pop() # 弹出最底层的节点            res.append(node.val)            node = node.right        return res[k - 1]</code></pre><p>这里的两个方法，其实都只是直接考虑全局排序的，时间消耗是很大的，从AC后的耗时可以看出，要优化的话，就要从这个k入手，记录并精准返回第k个然后程序结束。写一下：</p><pre><code class="language-python">class Solution:    def kthSmallest(self, root, k):        &quot;&quot;&quot;        :type root: TreeNode        :type k: int        :rtype: int        &quot;&quot;&quot;        self.k = k        self.res = None        self.helper(root)        return self.res    def helper(self, node):        if not node:            return        self.helper(node.left)        self.k -= 1 # 计数并精准返回        if self.k == 0:            self.res = node.val            return        self.helper(node.right)</code></pre><h2 id="Leetcode-69-Sqrt-x-开根号">Leetcode-69 Sqrt(x) 开根号</h2><blockquote><p>Implement int sqrt(int x).<br>Compute and return the square root of x, where x is guaranteed to be a non-negative integer.<br>Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.</p></blockquote><blockquote><p>Example 1:<br>Input: 4<br>Output: 2</p></blockquote><p>一道简单题，python可以导入math，或者用x**都可以得到结果，但是这不是目的。这是一题二分查找的题，考虑了一些边界条件和结束条件，就写了如下的一个方法，有点繁杂但是可以AC：</p><pre><code class="language-python">class Solution:    def mySqrt(self, x):        &quot;&quot;&quot;        :type x: int        :rtype: int        &quot;&quot;&quot;        if x == 0:            return 0                def calculate(i, j, x):            if i * i == x:                return i            if j * j == x:                return j            if j - i &lt;= 1:                return i            mid = (i + j) // 2            if mid * mid &gt; x:                return calculate(i, mid, x)            else:                return calculate(mid, j, x)        return calculate(1, x, x)</code></pre><p>我是故意写递归的，目的是让自己习惯。下面照着大神的做法，理解了写了个别的方法</p><pre><code class="language-python">class Solution:    def mySqrt(self, x):        l, r = 0, x        while l &lt;= r:            mid = l + (r-l)//2            if mid * mid &lt;= x &lt; (mid+1)*(mid+1):                return mid            elif x &lt; mid * mid:                r = mid            else:                l = mid + 1</code></pre><p>这里巧妙的在于，初始是0也就不需要额外判断了，然后把我上面的三个条件并成了一个，如果x在mid*mid和(mid+1) *（mid+1）之间即输出。不过这个题，只要想到二分查找就至少答到题意了，有些优化可能第一时间想不到也没关系。就这样吧。</p><h2 id="Leetcode-226-Invert-Binary-Tree-翻转二叉树">Leetcode-226 Invert Binary Tree 翻转二叉树</h2><blockquote><p>Invert a binary tree.<br>Example:<br>Input:<br>4<br>/   <br>2     7<br>/ \   / <br>1   3 6   9</p></blockquote><blockquote><p>Output:<br>4<br>/   <br>7     2<br>/ \   / <br>9   6 3   1</p></blockquote><p>这个题来源于一个小故事，Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off. 哈哈哈。这个题也是一个典型树类的递归应用，直接交换左右子树位置就好了。代码如下：</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode:#     def __init__(self, x):#         self.val = x#         self.left = None#         self.right = Noneclass Solution:    def invertTree(self, root):        &quot;&quot;&quot;        :type root: TreeNode        :rtype: TreeNode        &quot;&quot;&quot;        if not root:            return None        # tmp = root.left        root.left, root.right = root.right, root.left        self.invertTree(root.left)        self.invertTree(root.right)        return root</code></pre><p>还可以进一步简化为这样子：</p><pre><code class="language-python">class Solution:    def invertTree(self, root):        &quot;&quot;&quot;        :type root: TreeNode        :rtype: TreeNode        &quot;&quot;&quot;        if root:          invert = self.invertTree          root.left, root.right = invert(root.right), invert(root.left)          return root</code></pre><p>反正也是一个意思吧。<br>总体来说，树类的问题套路一般就是直上直下的遍历，采用递归或者while的循环都可以，遍历的3种模式中，前序遍历，中序遍历可以相互转化，后序遍历和层次遍历（BFS）可以一起记忆。另一类就是DFS，需要维护和记录一个visited数组。</p><h2 id="Leetcode-435-Non-overlapping-Intervals-不重复交集">Leetcode-435 Non-overlapping Intervals 不重复交集</h2><blockquote><p>Given a collection of intervals, find the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.<br>Note:<br>You may assume the interval’s end point is always bigger than its start point.<br>Intervals like [1,2] and [2,3] have borders “touching” but they don’t overlap each other.</p></blockquote><blockquote><p>Example 1:<br>Input: [ [1,2], [2,3], [3,4], [1,3] ]<br>Output: 1<br>Explanation: [1,3] can be removed and the rest of intervals are non-overlapping.</p></blockquote><p>这种题目，其实不难，但是如果一旦想偏了，就很难做对了。这里的问题是他有一个假设条件可以简化运算，就是每个interval是按大小排好的。然后这个题的trick就是可以做一次排序，这样可以避免两层的for循环嵌套，而且每个interval的关系定下了一层遍历更加清楚，其实这个已经想到了，只是一开始控制条件没有写对，耽误了很多时间。思路：记录一个end指针，如果下一个start &gt;= current_end，就更新这个end指针，否则就记录一个overlap，代码如下：</p><pre><code class="language-python"># Definition for an interval.# class Interval(object):#     def __init__(self, s=0, e=0):#         self.start = s#         self.end = eclass Solution(object):    def eraseOverlapIntervals(self, intervals):        &quot;&quot;&quot;        :type intervals: List[Interval]        :rtype: int        &quot;&quot;&quot;        if len(intervals) &lt;= 1:            return 0        intervals.sort(key = lambda x: x.end)        start, current_end = intervals[0].start, intervals[0].end        count = 0        for item in intervals[1:]:            if item.start &gt;= current_end:                current_end = item.end            else:                count += 1        return count</code></pre><p>AC了，这个题没什么特别的，就是要一开始想清楚。</p><h2 id="Leetcode-51-N-Queens-N皇后">Leetcode-51 N-Queens N皇后</h2><blockquote><p>The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other. Given an integer n, return all distinct solutions to the n-queens puzzle.</p></blockquote><blockquote><p>Each solution contains a distinct board configuration of the n-queens’ placement, where ‘Q’ and ‘.’ both indicate a queen and an empty space respectively.</p></blockquote><blockquote><p>Example:<br>Input: 4<br>Output: [<br>[“.Q…”,  // Solution 1<br>“…Q”,<br>“Q…”,<br>“…Q.”],<br>[“…Q.”,  // Solution 2<br>“Q…”,<br>“…Q”,<br>“.Q…”]<br>]<br>Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above.</p></blockquote><p>我觉得是一道必须掌握的难题，八皇后考验了整体的思维，我个人也想了很多，推倒了重来了好几次。</p><p>第一先审题，这里的问题是说要返回所有可能的解，而不是只是返回一个可能的解。这一点我折腾了很久，写完了发现有点问题。</p><p>第二，开始想模块，我不可能像大牛们可以一下子一步到位输出结果，所以想把每一个模块列出来：</p><ul><li>判断模块，3个情况，不在同一行，不在同一列，不在同一个斜线。这里的斜线要考虑2个方向：abs(r1-r2) == abs(c1 - c2)以及 r1+c1 == r2+c2 （checkValid()）。</li><li>主模块，递归加遍历，这里我比较优先采用DFS来收集(dfs())。</li><li>打印结果模块 (trans_print())。</li></ul><pre><code class="language-python">class Solution:    def __init__(self):        self.res = []        def checkValid(self, current, visited):        if not visited:            return True        curr_row = len(visited)        # 三个情况的判断        for idx, item in enumerate(visited):            if abs(item - current) == abs(idx - curr_row) or (idx + item) == current + curr_row or current == item:                return False        return True            def dfs(self, visited, n):        if len(visited) == n:            self.res.append(visited) # 结果收集            return         for col in range(n):            if self.checkValid(col, visited):                self.dfs(visited + [col], n) #！这里需要格外注意，只有visited +                                              #[col]这样可以支持程序回退        def trans_print(self, res, n):        if not res:            return []        final_res = []        for r in res: # 结果打印            res_list = []            for num in r:                temp = ['.'] * n                temp[num] = 'Q'                res_list.append(''.join(temp))            final_res.append(res_list)        return final_res                    def solveNQueens(self, n):        &quot;&quot;&quot;        :type n: int        :rtype: List[List[str]]        &quot;&quot;&quot;        self.dfs([], n)        return self.trans_print(self.res, n)</code></pre><p>我这里是写的比较啰嗦的，其实是为了看的清楚一些，其中很关键的一点是visited + [col]，不能直接写生visited += [col]，因为在下一次传递参数的时候，不成功不会改变visited的值，这样做可以简化我们的回退操作，比如我们一直递归下去会出现visited + [col] + [col] + [col]，一旦这时条件判断不成立需要回退，那么我们仍然可以退回到上一次的遍历即visited + [col] + [col]。这个技巧我看该题的Discussion里也都是这样做的。另外贴一下大神的解法：</p><pre><code class="language-python">def solveNQueens(self, n):    def DFS(queens, xy_dif, xy_sum):        p = len(queens)        if p==n:            result.append(queens)            return None        for q in range(n):            if q not in queens and p-q not in xy_dif and p+q not in xy_sum:                 DFS(queens+[q], xy_dif+[p-q], xy_sum+[p+q])      result = []    DFS([],[],[])    return [ [&quot;.&quot;*i + &quot;Q&quot; + &quot;.&quot;*(n-i-1) for i in sol] for sol in result]</code></pre><p>他这里用到的方法和我也比较类似，也是只存储列的值，行值用index表示，巧妙地在于传递的是累加的验证条件，还有最后的打印结果都是值得学习的地方。</p><h2 id="Leetcode-19-Remove-Nth-Node-From-End-of-List-末尾移除节点">Leetcode-19 Remove Nth Node From End of List 末尾移除节点</h2><blockquote><p>Given a linked list, remove the n-th node from the end of list and return its head.<br>Example:<br>Given linked list: 1-&gt;2-&gt;3-&gt;4-&gt;5, and n = 2.<br>After removing the second node from the end, the linked list becomes 1-&gt;2-&gt;3-&gt;5.</p></blockquote><p>我其实觉得链表题我挺抓不到感觉的，很多时候都觉得遇到会就是会，不想别的题，怎么都能有个思路。这一题就是一道典型的快慢指针题，问题是删除末尾第N个，所以当快指针到达链表尾部，对应删除重新定义慢指针的Next元素就基本搞定了：</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode:#     def __init__(self, x):#         self.val = x#         self.next = Noneclass Solution:    def removeNthFromEnd(self, head, n):        &quot;&quot;&quot;        :type head: ListNode        :type n: int        :rtype: ListNode        &quot;&quot;&quot;        ahead, behind = head, head        mark = n        while mark &gt; 0:            ahead = ahead.next            mark -= 1        if not ahead:            return head.next        while ahead.next:            ahead = ahead.next            behind = behind.next        behind.next = behind.next.next        return head</code></pre><p>如果N大于等于了我们链表的长度，也就是要删除链表头部的结点了，这是就直接把头结点砍掉，返回剩下的就可以了，这是属于边界条件的考察，需要注意。</p><h2 id="Leetcode-235-Lowest-Common-Ancestor-of-a-Binary-Search-Tree-BST最低公共祖先">Leetcode-235 Lowest Common Ancestor of a Binary Search Tree BST最低公共祖先</h2><blockquote><p>Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.</p></blockquote><blockquote><p>According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”</p></blockquote><blockquote><p>Given binary search tree:  root = [6,2,8,0,4,7,9,null,null,3,5]</p></blockquote><blockquote><p>Example 1:<br>Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8<br>Output: 6<br>Explanation: The LCA of nodes 2 and 8 is 6.</p></blockquote><blockquote><p>Example 2:<br>Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4<br>Output: 2<br>Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.</p></blockquote><p>题目是一道BST的题，一看到BST题就要想到左小右大的性质，一般树类的问题都是采用递归，这题也不例外，分析题目：给定P和Q，要找到其最低的公共祖先，公共祖先也可以为它自己本身。条件可以得知自顶向下遍历，如果落在P和Q之间就为其公共祖先，反之则要通过最大和最小值来进行判断，如果root值已经大于P和Q的最大值，则将左子树带入递归遍历，如果root值小于P和Q最小值，则将右子树带入递归遍历。代码如下：</p><pre><code class="language-python"># Definition for a binary tree node.# class TreeNode(object):#     def __init__(self, x):#         self.val = x#         self.left = None#         self.right = Noneclass Solution(object):    def lowestCommonAncestor(self, root, p, q):        &quot;&quot;&quot;        :type root: TreeNode        :type p: TreeNode        :type q: TreeNode        :rtype: TreeNode        &quot;&quot;&quot;        max_num = max(p.val, q.val)        min_num = min(p.val, q.val)        if root.val &gt;= min_num and root.val &lt;= max_num:            return root        if root.val &gt; max_num:            return self.lowestCommonAncestor(root.left, p, q)        else:            return self.lowestCommonAncestor(root.right, p, q)</code></pre><p>贴个大神方法：</p><pre><code class="language-python">def lowestCommonAncestor(self, root, p, q):    a, b = sorted([p.val, q.val])    while not a &lt;= root.val &lt;= b:        root = (root.left, root.right)[a &gt; root.val]    return root</code></pre><p>这里用<code>[a &gt; root.val]</code>来选择左右子树，成立则为True, True就是1，则指向右子树。繁殖False为0，传入左子树，哈哈哈，算是个小小的骚操作吧。总之还是比较送分的一题。</p><h2 id="Leetcode-207-Course-schedule">Leetcode-207 Course schedule</h2><blockquote><p>There are a total of n courses you have to take, labeled from 0 to n-1.<br>Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]<br>Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?</p></blockquote><blockquote><p>Example 1:<br>Input: 2, [[1,0]]<br>Output: true<br>Explanation: There are a total of 2 courses to take.<br>To take course 1 you should have finished course 0. So it is possible.</p></blockquote><blockquote><p>Example 2:<br>Input: 2, [[1,0],[0,1]]<br>Output: false<br>Explanation: There are a total of 2 courses to take.<br>To take course 1 you should have finished course 0, and to take course 0 you should<br>also have finished course 1. So it is impossible.</p></blockquote><p>又是一道并不简单的拓扑排序题，是一个检测是否为DAG的问题。由于课程和课程之间形成了依赖，所以如果我们要能成功选择所有的课，那么就意味着所有课之间的依赖不能有环，也就是不能循环依赖。所以我们可以用图来表示整个过程，类似于这样<br><img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSlzRqIj3mvgCx4JTisjXW8qhL9RSOJjF3x8oOHucCIost4WgYS" alt="IMAGE"></p><p>要表示这个图，我们当然可以采取如题示的那样每一个边都独立表示，但是那样实在是不够直观，因此借助图论的知识，我们用顶点（vertice）建立字典来存储整个数据结构，上述的图就可以表示为</p><pre><code class="language-python"># out(出顶点) -&gt; in(入顶点){  0: [1, 2],  1: [3],  2: [3]}# in(入顶点) -&gt; out(出顶点){  1: [0],  2: [0],  3: [1, 2]}</code></pre><p>有了图的表示之后，就自然引出了2个非常经典的拓扑排序解法，一个是BFS，一个是DFS。<br>先说BFS，由于图的表示，让BFS不那么的直观，直觉都会想着去顺着有向图来遍历，我一开始也在想这东西怎么做BFS，网上搜到了一个kahn’s算法，读了一下立马就直观多了:</p><blockquote><p>其实就是不断的寻找有向图中没有前驱(入度为0)的顶点，将之输出。然后从有向图中删除所有以此顶点为尾的弧。重复操作，直至图空，或者找不到没有前驱的顶点为止。<br>该算法还可以判断有向图是否存在环(存在环的有向图肯定没有拓扑序列)，通过一个count记录找出的顶点个数，如果少于N则说明存在环使剩余的顶点的入度不为0。（degree数组记录每个点的入度数）</p></blockquote><p>基本有了一个概念之后，就要来着手自己实现了，首先明确需要借助的存储空间，为了直观我大概列了三个：</p><ol><li>入度为0的顶点的集合（每次遍历图，如果图顶点的后继节点不存在，那就移入入度为0的集合）</li><li>遍历过已经放入的结果集（遍历过的放入结果中）</li><li>当前图（字典，当移除入度为0的顶点，同步也要将图中的边进行更新，同步移除包含该入度为0顶点对应的边，再进行判断，如果移除后顶点本身同样没有进入的边了即值为空数组，便也要将其更新到入度为0顶点的集合中，以便继续遍历结束）。</li></ol><p>结束的条件就是最终我们能否将整个图都消灭完毕，如果不能则证明图中含有环。<br>我依旧自己先比较丑陋的实现了一下：</p><pre><code class="language-python">class Solution:    def canFinish(self, numCourses, prerequisites):        &quot;&quot;&quot;        :type numCourses: int        :type prerequisites: List[List[int]]        :rtype: bool        &quot;&quot;&quot;                ordered = [] # 存结果                graph = collections.defaultdict(list) #表示图        for out, into in prerequisites:            graph[into].append(out)                    zero_in_degree = [i for i in range(numCourses) if i not in graph] # 存入度为0的顶点集合            for item in zero_in_degree:            for key, val in graph.items():                if item in val: # 图中含有该节点准备删除                    val.remove(item)  # 移除该顶点                    if len(val) == 0: # 该顶点入度为0，则将该节点插入到0 degree 集合 下次继续遍历                        zero_in_degree.append(key)             ordered.append(item)        return len(ordered) == numCourses</code></pre><p>可以AC但是很慢，改良一下不用字典来存储</p><pre><code class="language-python">class Solution:    def canFinish(self, n, prerequisites):        G = [[] for i in range(n)] #表示图        degree = [0] * n         for j, i in prerequisites:            G[i].append(j)            degree[j] += 1                    bfs = [i for i in range(n) if degree[i] == 0]        for i in bfs:            for j in G[i]:                degree[j] -= 1                if degree[j] == 0:                    bfs.append(j)        return len(bfs) == n</code></pre><p>这里他是从后往前删除的，degree[j] += 1 表示没有指出的顶点，先移除，然后再改变图中节点。比较巧妙的是只用了数组来存储，可能不是很直观，但是省去了字典删除操作，很高效。</p><p>另外就是用DFS，借助一个visit数组来保存当下访问节点的状态，初始化为0，正在访问为-1，访问结束为1表明安全无环，一旦深度遍历的过程中遇到visit值为-1，就表明图中有环存在，终止跑出False。代码如下：</p><pre><code class="language-python">def canFinish(self, numCourses, prerequisites):    graph = [[] for _ in xrange(numCourses)]    visit = [0 for _ in xrange(numCourses)]    for x, y in prerequisites:        graph[x].append(y)    def dfs(i):        if visit[i] == -1:            return False        if visit[i] == 1:            return True        visit[i] = -1        for j in graph[i]:            if not dfs(j):                return False        visit[i] = 1        return True    for i in range(numCourses):        if not dfs(i):            return False    return True</code></pre><p>因为有一定的剪枝存在，所以效率相较于BFS会高一些。以上。</p><h2 id="Leetcode-309-Best-Time-to-Buy-and-Sell-Stock-with-Cooldown-股票买卖">Leetcode-309 Best Time to Buy and Sell Stock with Cooldown 股票买卖</h2><blockquote><p>Say you have an array for which the ith element is the price of a given stock on day i. Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:</p></blockquote><blockquote><p>You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).<br>After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)<br>Example:</p></blockquote><blockquote><p>Input: [1,2,3,0,2]<br>Output: 3<br>Explanation: transactions = [buy, sell, cooldown, buy, sell]</p></blockquote><p>买股票题是一道经典的01背包变种问题，这些题有些可以变到你完全懵逼，我这里参考了花花酱算法的思路，是我看下来讲的最清楚的一个，我自己感觉是不太容易想到。</p><p>先来把概念分析做好，首先这里牵涉到3个动作：buy（买），sell（卖），rest（什么都不做）。然后对应的状态是：hold（买入持有状态），sold（卖出状态），rest（空仓状态）。在这个问题中，我们关心的是手上的资产，因此状态转移方程应该围绕hold，sold和rest3个状态来展开，01背包只有一个状态转移关系，可见这个问题还是比较复杂的。</p><p>根据题意，结合FSM（状态机）的概念，可以把对应的3个转移方程写出来：</p><ol><li><strong>hold[i] = max(hold[i-1], reset[i-1] - price[i])</strong><br>持有状态的价值 = max(前一时刻持有的价值，当前时刻买入的价值)，由于买入之前一定要有cooldown，所以会产生第二种状态转变，注意买入的话是花钱，所以是减去当前股价。</li><li><strong>sold[i] = hold[i-1] + price[i]</strong><br>卖出状态的价值 = 前一时刻的持有和当前卖出加钱的和，这里hold[i-1]因为是买入花钱，就一定是负数，所以是求和。</li><li><strong>rest[i] = max(rest[i-1], sold[i-1])</strong><br>空仓状态的价值 = max(前一时刻空仓价值，卖出之后的价值）。因为卖出后必须要cooldown，所以这里的sold[i-1]也可以理解了。</li></ol><p>上述三个状态的图得到如下：<br><img src="https://img-blog.csdnimg.cn/20190110184947556.png" alt="IMAGE"></p><p>在初始值这里，hold[0] = -inf, sold[0] = 0, rest[0] = 0，这里hold的初始值不可以为0，因为买入是花钱持有的一定是负资产。</p><p>最终状态里，由于我们最后一个时刻不能买入股票，所以最优的状态一定是出现在rest和sold中，即要不最后一天正好卖出，要不最后一天依旧空仓。</p><p>然后我就可以非常顺畅的写下来代码了，一遍过，可见思想是何其重要，借助一个状态机解决了这么大的问题。</p><pre><code class="language-python">class Solution(object):    def maxProfit(self, prices):        &quot;&quot;&quot;        :type prices: List[int]        :rtype: int        &quot;&quot;&quot;        hold = [float('-inf')] * (len(prices) + 1)        sold = [0] * (len(prices) + 1)        rest = [0] * (len(prices) + 1)        prices.insert(0, None)                for i in range(1, len(prices)):            hold[i] = max(hold[i-1], rest[i-1] - prices[i])            sold[i] = hold[i-1] + prices[i]            rest[i] = max(rest[i-1], sold[i-1])                    return max(sold[-1], rest[-1])</code></pre><p>好像也可以写递归，也是差不多的意思了，有了状态转移方程什么都好办了，这里就不赘述了。</p><h2 id="Leetcode-64-Minimum-Path-Sum-最小路径和">Leetcode-64 Minimum Path Sum 最小路径和</h2><blockquote><p>Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.</p></blockquote><blockquote><p>Note: You can only move either down or right at any point in time.</p></blockquote><blockquote><p>Example:<br>Input:<br>[<br>[1,3,1],<br>[1,5,1],<br>[4,2,1]<br>]<br>Output: 7<br>Explanation: Because the path 1→3→1→1→1 minimizes the sum.</p></blockquote><p>经典的DP题，脑子里立马会想到采用递归或者动态规划，递归自定向下，动态规划自下而上。<br>递归的思维就应该是递归用最终状态往前推，直到初始条件后输出结果。<br>动态规划的思维就是把大问题小规模化，借助2维数组来记录每一个小规模问题最优解，逐步递推到我们所求的解，说白了也就这么多东西。关键在于写出状态转移方程：<br>$$ route[i][j] = pos[i][j] + min(route[i-1][j], route[i][j-1])$$</p><p>解法一，递归+备忘录</p><pre><code class="language-python">class Solution(object):    def minPathSum(self, grid):        &quot;&quot;&quot;        :type grid: List[List[int]]        :rtype: int        &quot;&quot;&quot;        mem = {} # 备忘录        # route[i][j] = pos[i][j] + min(route[i-1][j], route[i][j-1])                def route(i, j):            if i &lt; 0 or j &lt; 0: # 超出边界时，我会主动返回一个正无穷给min()去做判断                return float('inf')                        if i == 0 and j == 0:                return grid[i][j]                        if (i, j) in mem:                return mem[(i, j)]            else:                mem[(i, j)] = grid[i][j] + min(route(i-1, j), route(i, j-1))                return mem[(i, j)]                if not len(grid) or not len(grid[0]):            return 0        return route(len(grid) - 1, len(grid[0]) - 1)</code></pre><p>解法二，DP+二维数组</p><pre><code class="language-python">def minPathSum(self, grid):    m = len(grid)    n = len(grid[0])    # 补齐首行和首列    for i in range(1, n):        grid[0][i] += grid[0][i-1]    for i in range(1, m):        grid[i][0] += grid[i-1][0]    for i in range(1, m):        for j in range(1, n):            grid[i][j] += min(grid[i-1][j], grid[i][j-1])    return grid[-1][-1]</code></pre><p>这是我摘取的一个大神的解法，写得贼清楚简洁，而且容易读懂，这才是写算法应该最终追求的。这边没有另外开辟存储空间，就直接在grid上做了输出，这里对第一行和第一列做了预处理，然后从(1,1)开始计算，最终输出右下角的结果。</p><p>这个题有时候还会不只是叫你输出最小值和，可能会让你列出最小值路径的详细情况，或者所有详细情况，这里可以拿到DP的解反推回去做一遍记录。另外也有其他变种的求法，比如从一点到另一点有几种走法，这个有点像爬楼梯问题，就不做过多介绍了。</p><h2 id="Leetcode-763-Partition-Labels-分割字符">Leetcode-763 Partition Labels 分割字符</h2><blockquote><p>A string S of lowercase letters is given. We want to partition this string into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing the size of these parts.</p></blockquote><blockquote><p>Example 1:<br>Input: S = “ababcbacadefegdehijhklij”<br>Output: [9,7,8]</p></blockquote><blockquote><p>Explanation:<br>The partition is “ababcbaca”, “defegde”, “hijhklij”.<br>This is a partition so that each letter appears in at most one part.<br>A partition like “ababcbacadefegde”, “hijhklij” is incorrect, because it splits S into less parts.</p></blockquote><p>这题有点懵逼的，一开始没什么思路，然后大致读懂了题意之后，脑子里又变得很乱，各种时间复杂度很高的算法都涌了出来，其实都没什么用。然后看到了这个题属于双指针和贪心，似乎给了我一点提示。借助一个字典来存储每个字母最后出现的索引，采用start和end双指针切割的思想，一旦end指针达到切割点便将数组切割，然后更新start节点以便下一次循环遍历得到。代码如下</p><pre><code class="language-python">import collectionsclass Solution(object):    def partitionLabels(self, S):        &quot;&quot;&quot;        :type S: str        :rtype: List[int]        &quot;&quot;&quot;        last_pos = {}        for idx, item in enumerate(list(S)):            last_pos[item] = idx                    res = []        start, end = 0, 0                for index, item in enumerate(S):            if last_pos[item] &gt; end:                end = last_pos[item]            # 到达切割点            if index == end:                res.append(end - start + 1)                start = end + 1        return res</code></pre><p>这题复盘的时候，我在想怎么自己第一遍就不会想到这个思路呢，这题的关键无非就是了解题意之后。自己之前想用count来直接计数，然后发现边界条件很难写好，语句也不是很容易读，多了很多ifelse判断，但是用双指针以扩充范围的思想来看，就立马写的很顺了。</p><h2 id="Leetcode-232-Implement-Queue-using-Stacks-用堆栈实现队列">Leetcode-232 Implement Queue using Stacks 用堆栈实现队列</h2><blockquote><p>Implement the following operations of a queue using stacks.</p></blockquote><blockquote><p>push(x) – Push element x to the back of queue.<br>pop() – Removes the element from in front of queue.<br>peek() – Get the front element.<br>empty() – Return whether the queue is empty.<br>Example:</p></blockquote><blockquote><p>MyQueue queue = new MyQueue();<br>queue.push(1);<br>queue.push(2);<br>queue.peek();  // returns 1<br>queue.pop();   // returns 1<br>queue.empty(); // returns false</p></blockquote><p>这是一道用栈来实现队列的问题，作为两种比较基本的数据结构，栈的特性是后进先出（LIFO），队列的特性是先进先出（FIFO）。因此这道题要实现的操作中，主要就是要实现push和pop不同数据结构之间的操作转化。</p><p>这里区别最大的一点就是push操作，堆栈只能放在顶部，因此pop时会出错，于是我们需要将push的元素放到头部，这样就和队列的数据结构保持一致了。这里借助另一个数组来实现这个功能，参考图：<br><img src="https://leetcode.com/media/original_images/232_queue_using_stacksBPush.png" alt="IMG"></p><p>代码：</p><pre><code class="language-python">class MyQueue:    def __init__(self):        self.s1 = []        self.s2 = []    def push(self, x):        while self.s1:            self.s2.append(self.s1.pop())        self.s1.append(x)        while self.s2:            self.s1.append(self.s2.pop())    def pop(self):        return self.s1.pop()    def peek(self):        return self.s1[-1]    def empty(self):        return not self.s1</code></pre><h2 id="Leetcode-975-Odd-Even-Jump-奇偶跳">Leetcode-975 Odd Even Jump 奇偶跳</h2><blockquote><p>You are given an integer array A.  From some starting index, you can make a series of jumps.  The (1st, 3rd, 5th, …) jumps in the series are called odd numbered jumps, and the (2nd, 4th, 6th, …) jumps in the series are called even numbered jumps.</p></blockquote><blockquote><p>You may from index i jump forward to index j (with i &lt; j) in the following way:<br>During odd numbered jumps (ie. jumps 1, 3, 5, …), you jump to the index j such that A[i] &lt;= A[j] and A[j] is the smallest possible value.  If there are multiple such indexes j, you can only jump to the smallest such index j.<br>During even numbered jumps (ie. jumps 2, 4, 6, …), you jump to the index j such that A[i] &gt;= A[j] and A[j] is the largest possible value.  If there are multiple such indexes j, you can only jump to the smallest such index j.<br>(It may be the case that for some index i, there are no legal jumps.)<br>A starting index is good if, starting from that index, you can reach the end of the array (index A.length - 1) by jumping some number of times (possibly 0 or more than once.)</p></blockquote><blockquote><p>Return the number of good starting indexes.</p></blockquote><p>作死做了一道这么难的题，题目我都懒得摘下来了，太长了，有点 ACM的感觉。最近发现其实难题之所以难，一般是结合了2个以上的知识点，也可以说是一道题考察多个方面，代码层面也需要构建多个模块。我这里参考了花花酱的视频讲解 <a href="https://zxi.mytechroad.com/blog/dynamic-programming/leetcode-975-odd-even-jump/" target="_blank" rel="noopener">Link</a>。</p><p>给定一个整数数组 A，你可以从某一起始索引出发，跳跃一定次数。在你跳跃的过程中，第 1、3、5… 次跳跃称为奇数跳跃，而第 2、4、6… 次跳跃称为偶数跳跃。</p><p>你可以按以下方式从索引 i 向后跳转到索引 j（其中 i &lt; j）：<br>在进行奇数跳跃时（如，第 1，3，5… 次跳跃），你将会跳到索引 j，使得 A[i] &lt;= A[j]，A[j] 是可能的最小值。如果存在多个这样的索引 j，你只能跳到满足要求的最小索引 j 上。<br>在进行偶数跳跃时（如，第 2，4，6… 次跳跃），你将会跳到索引 j，使得 A[i] =&gt; A[j]，A[j] 是可能的最大值。如果存在多个这样的索引 j，你只能跳到满足要求的最小索引 j 上。<br>（对于某些索引 i，可能无法进行合乎要求的跳跃。）<br>如果从某一索引开始跳跃一定次数（可能是 0 次或多次），就可以到达数组的末尾（索引 A.length - 1），那么该索引就会被认为是好的起始索引。</p><p>输入：[2,3,1,1,4]<br>输出：3<br>解释：<br>从起始索引 i=0 出发，我们依次可以跳到 i = 1，i = 2，i = 3：<br>在我们的第一次跳跃（奇数）中，我们先跳到 i = 1，因为 A[1] 是（A[1]，A[2]，A[3]，A[4]）中大于或等于 A[0] 的最小值。<br>在我们的第二次跳跃（偶数）中，我们从 i = 1 跳到 i = 2，因为 A[2] 是（A[2]，A[3]，A[4]）中小于或等于 A[1] 的最大值。A[3] 也是最大的值，但 2 是一个较小的索引，所以我们只能跳到 i = 2，而不能跳到 i = 3。<br>在我们的第三次跳跃（奇数）中，我们从 i = 2 跳到 i = 3，因为 A[3] 是（A[3]，A[4]）中大于或等于 A[2] 的最小值。<br>我们不能从 i = 3 跳到 i = 4，所以起始索引 i = 0 不是好的起始索引。<br>类似地，我们可以推断：<br>从起始索引 i = 1 出发， 我们跳到 i = 4，这样我们就到达数组末尾。<br>从起始索引 i = 2 出发， 我们跳到 i = 3，然后我们就不能再跳了。<br>从起始索引 i = 3 出发， 我们跳到 i = 4，这样我们就到达数组末尾。<br>从起始索引 i = 4 出发，我们已经到达数组末尾。<br>总之，我们可以从 3 个不同的起始索引（i = 1, i = 3, i = 4）出发，通过一定数量的跳跃到达数组末尾。</p><p>这道题，第一步重在列出递推公式。我们从后向前遍历：<br>如果第i个节点可以上升跳到队尾，那么只要前序的节点j能下降跳到i节点就算是成功True。<br>如果第i个节点可以下降跳到队尾，那么只要前序的节点j能上升跳到i节点就算是成功True。<br>所以递推公式可以得到一个间隔的形式：<br>odd[i] = even[j]<br>even[i] = odd[j]</p><p>因为要反应出递推的关系，所以我们加上另一个替代j的关系式：<br>odd[i] = even[odd_next[i]]<br>even[i] = odd[even_next[i]]<br>这里的<code>odd_next</code>和<code>even_next</code>反应那个odd和even的跳跃寻找符合条件的关系。其实能想到现在这一步，不算太难，但是在寻找奇偶分别条件上卡了很久，我只能想到一般的扫描，写的也不是很好看，说到底还是不熟。最后参考了大神的写法，发现小技巧其实很多，从倒序用reserved，到负数排序满足降序，再到用stack的退栈。最后，由于我们只能算odd的总和，因为每一个数都是从第一步开始跳算起的。<br>贴一下代码：</p><pre><code class="language-python">class Solution:    def oddEvenJumps(self, A):        &quot;&quot;&quot;        :type A: List[int]        :rtype: int        &quot;&quot;&quot;        odd = [False] * len(A)        even = [False] * len(A)                # 最后一个元素一定是满足的        odd[-1], even[-1] = True, True                oddNext = [None] * len(A)        evenNext = [None] * len(A)                # 用一个stack来存储最优值（最大或者最小）        # 返回出对应原数组的index, stack也是装的index        stack = []        for item, index in sorted([[item, index] for index, item in enumerate(A)]):            while stack and index &gt; stack[-1]:                 oddNext[stack.pop()] = index            stack.append(index)                    stack = []        for item, index in sorted([[-item, index] for index, item in enumerate(A)]):            while stack and index &gt; stack[-1]:                 evenNext[stack.pop()] = index            stack.append(index)                    for i in reversed(range(len(A) - 1)):            if oddNext[i] is not None:                odd[i] = even[oddNext[i]]            if evenNext[i] is not None:                even[i] = odd[evenNext[i]]        return sum(odd)</code></pre><h2 id="Leetcode-486-Predict-the-Winner-预测胜者">Leetcode-486 Predict the Winner 预测胜者</h2><blockquote><p>Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from either end of the array followed by the player 2 and then player 1 and so on. Each time a player picks a number, that number will not be available for the next player. This continues until all the scores have been chosen. The player with the maximum score wins.</p></blockquote><blockquote><p>Given an array of scores, predict whether player 1 is the winner. You can assume each player plays to maximize his score.</p></blockquote><blockquote><p>Example 1:<br>Input: [1, 5, 2]<br>Output: False<br>Explanation: Initially, player 1 can choose between 1 and 2.<br>If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2).<br>So, final score of player 1 is 1 + 2 = 3, and player 2 is 5.<br>Hence, player 1 will never be the winner and you need to return False.</p></blockquote><blockquote><p>Example 2:<br>Input: [1, 5, 233, 7]<br>Output: True<br>Explanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.<br>Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.<br>1 5 233 8</p></blockquote><p>这道题，刚看有点没有太get到题意，这道题是一道有点博弈论背景的minmax的题，我也是看了花花酱的讲解有了一点认识。这里不能直接去拿最优的解，而是要转化成player1和player2的差来解决，因为题目只要求出player1可否胜利，因此，如果存在player1 - player2的差值大于0的话，就代表player1能成功。这里有一个坑，就是两个队员都要去尽量拿最优的结果，而不是只是那存在的结果，换句话说就是不能作弊。我刚开始看这道题的时候，也在想比如[1, 5, 2]中player1拿2，player2拿1，player1再拿5，这样player1也能赢。但是题设里有提到每个人都要尽量使得分数最大化。</p><p>既然两个选手都是比较聪明的，那我们也聪明一点。在这里的问题中，如果数字的个数是偶数个的化，player1一定可以赢，因为数字是可见的，player1有先手的优势，因此他完全可以找到预先找到最大的值安排顺序，换句话说就是他可以预知结果，比如[1, 5, 233, 7]，player1可以知道233是最大值，因此他可以预先让自己能最终拿到这个数字，那么他为了保证一定能拿到233，那么他第一次就一定要拿1，这样player2只能从5和7中选择，如此就可以顺利成为赢家，其他偶数数字更大的情况也是一样适用的。而奇数就会存在不确定性，这个不确定性和player1多拿的数字有关。同之前的例子，如果这里数字变成了5个，那么也就是说player1会多取一个数字，剩下4个数字先手是player2，所以player2在偶数个的情况下是绝对可以保证自己赢的，那么player1到底能否最终成为赢家取决于，player1多取一个数字能否赢过偶数个情况下player2最优的情况。</p><p>这里的递推公式，可以写成<br>$$ solve(nums) = max(nums[0] - solve(nums[1:]), nums[-1] - solve(nums[:-1])) $$</p><p>解释一下，solve(nums)是每次最优的情况，而nums[0]和nums[-1]代表前一次取值的可能，而player1要赢，那么最后的solve的结果一定要大于等于0，才能赢。采用记忆化递归的方式，代码如下：</p><pre><code class="language-python">class Solution(object):    def PredictTheWinner(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: bool        &quot;&quot;&quot;        if len(nums)%2 == 0 or len(nums) == 1:            return True                    cache = dict()                def solve(nums):                        tnums = tuple(nums)            if tnums in cache:                 return cache[tnums]            cache[tnums] = max(                nums[0] - solve(nums[1:]),                 nums[-1] - solve(nums[:-1])            )            return cache[tnums]        return solve(nums) &gt;= 0</code></pre><p>这么看代码不难，但是这个思路很难获得，也有用bottom-up做的，我没太看懂。。。感觉也有点复杂，列表格数组，就是不太容易想，直观是挺直观的。有时间再研究。</p><h2 id="Leetcode-551-Student-Attendance-Record-I-学生出席率">Leetcode-551 Student Attendance Record I 学生出席率</h2><blockquote><p>You are given a string representing an attendance record for a student. The record only contains the following three characters:<br>‘A’ : Absent.<br>‘L’ : Late.<br>‘P’ : Present.<br>A student could be rewarded if his attendance record doesn’t contain more than one ‘A’ (absent) or more than two continuous ‘L’ (late).</p></blockquote><blockquote><p>You need to return whether the student could be rewarded according to his attendance record.</p></blockquote><blockquote><p>Example 1:<br>Input: “PPALLP”<br>Output: True<br>Example 2:<br>Input: “PPALLL”<br>Output: False</p></blockquote><p>感觉是道送分题，随便写了一下就AC了，这里需要注意的是缺席全局不能超过1次，迟到不能连续大于2次，直接就上代码了，思路也都是比较平铺直叙的。</p><pre><code class="language-python">class Solution(object):    def checkRecord(self, s):        &quot;&quot;&quot;        :type s: str        :rtype: bool        &quot;&quot;&quot;        count_a = 0        count_l = 0        max_l = 0        for item in list(s):            if item == 'A':                count_a += 1                count_l = 0            elif item == 'L':                count_l += 1                if count_l &gt; max_l: max_l = count_l            else:                count_l = 0        return count_a &lt; 2 and count_l &lt; 3</code></pre><p>这里可以稍微剪枝改进一下，在循环里一旦遇到缺席大于1次或者迟到连续达到3次的就直接输出False，否则则为True，代码如下：</p><pre><code class="language-python">class Solution(object):    def checkRecord(self, s):        &quot;&quot;&quot;        :type s: str        :rtype: bool        &quot;&quot;&quot;        count_a = 0        count_l = 0        for item in list(s):            if item == 'A':                count_a += 1            if item == 'L':                count_l += 1            else: count_l = 0            if count_a &gt; 1 or count_l &gt; 2:                return False        return True</code></pre><h2 id="Leetcode-1-2SUM-2数求和">Leetcode-1 2SUM 2数求和</h2><blockquote><p>Given an array of integers, return indices of the two numbers such that they add up to a specific target.</p></blockquote><blockquote><p>You may assume that each input would have exactly one solution, and you may not use the same element twice.</p></blockquote><blockquote><p>Example:<br>Given nums = [2, 7, 11, 15], target = 9,<br>Because nums[0] + nums[1] = 2 + 7 = 9,<br>return [0, 1].</p></blockquote><p>哈哈哈，又做回了第一题，是想借这个机会把所有的k-sum类题目都过一遍，先从这个2sum开始。作为第一题实在是有点简单，因为这里要返回原数组的索引，所以用一个dict来保存数据，key为数字，value为索引，代码如下：</p><pre><code class="language-python">class Solution:    def twoSum(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: List[int]        &quot;&quot;&quot;        mem = dict()                for index, item in enumerate(nums):            if item in mem:                return [mem[item], index]            else:                mem[target - item] = index</code></pre><p>这里是一个基本款，但是有一个适用于这里sum问题所有的情况的就是双指针，这里也不例外，虽然是有点麻烦，但是还是很有必要写一下的。</p><pre><code class="language-python">class Solution(object):    def twoSum(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: List[int]        &quot;&quot;&quot;        sn = sorted((num, i) for i, num in enumerate(nums))        i, j = 0, len(nums) - 1        print(sn)        while i &lt; j:            n, k = sn[i]            m, l = sn[j]            if n + m &lt; target:                i += 1            elif n + m &gt; target:                j -= 1            else:                return [k, l]</code></pre><p>这里将原数组，转化为(value, index)一个tuple类型。对这个tuple类型进行了排序，这样既保留了原有的index和value，又排了顺序。然后就是常规的步骤了，两个指针各指向一头，如果大于target就移动右指针，反之移动左指针。最近发现很多问题都有涉及对这个tuple的运用，真的是一个很高阶的技能。</p><h2 id="Leetcode-15-3SUM-3数求和">Leetcode-15 3SUM 3数求和</h2><blockquote><p>Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.</p></blockquote><blockquote><p>Note: The solution set must not contain duplicate triplets.</p></blockquote><blockquote><p>Example:<br>Given array nums = [-1, 0, 1, 2, -1, -4],<br>A solution set is:<br>[<br>[-1, 0, 1],<br>[-1, -1, 2]<br>]</p></blockquote><p>和2Sum不同，这里是返回满足条件且不重复的结果，存储在一个list of lists里，既然是3Sum问题就会涉及到3个指针 ε==(づ′▽`)づ，看来是几sum就几个指针啊。这里比较棘手的是处理这个重复的问题，这里一度让我很头大，不过看了一些大神的方法，大家似乎也都是差不多的解决方案，那麻烦就麻烦点吧，代码如下：</p><pre><code class="language-python">class Solution:    def threeSum(self, nums):        &quot;&quot;&quot;        :type nums: List[int]        :rtype: List[List[int]]        &quot;&quot;&quot;        res = []        nums.sort()        # 外层第1个指针        for i in range(len(nums)-2):            if i &gt; 0 and nums[i] == nums[i-1]:                continue            # 第2第3个指针从i往后开始            l, r = i+1, len(nums)-1            while l &lt; r:                s = nums[i] + nums[l] + nums[r]                if s &lt; 0:                    l +=1                 elif s &gt; 0:                    r -= 1                else:                    res.append((nums[i], nums[l], nums[r]))                    # 这里判断直到找到下一个不同于上一个的值                    while l &lt; r and nums[l] == nums[l+1]:                        l += 1                    while l &lt; r and nums[r] == nums[r-1]:                        r -= 1                    l += 1; r -= 1 # 这里把最终移动放到最后        return res</code></pre><p>这里3个指针都需要去重，外层的i，去重判断为nums[i] == nums[i-1]，内部的i和j，当满足条件时，插入结果，立马开始去重，直到找到下一个不重复的位置再继续带入while循环，直到 l==r 停止循环。</p><h2 id="Leetcode-18-4SUM-4数求和">Leetcode-18 4SUM 4数求和</h2><blockquote><p>Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.</p></blockquote><blockquote><p>Note: The solution set must not contain duplicate quadruplets.</p></blockquote><blockquote><p>Example:<br>Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.<br>A solution set is:<br>[<br>[-1,  0, 0, 1],<br>[-2, -1, 1, 2],<br>[-2,  0, 0, 2]<br>]</p></blockquote><p>4数求和啦，一路从2个做到4个，这里也是一样的意思，4数就是4个指针喽（噗~(oﾟ▽ﾟ)o）。一样也是会遇到很恶心的重复问题，我这里先自己丑陋的实现了一遍，思路是外层2个指针，内部做2SUM，去重我借助了一个字典，因为重复的情况很多很复杂，所以借助字典可以照顾到所有的情况，内外一旦发现重复，就启动指针的移位，最后输出结果中的每一个都先排了个序，再set去重，再转成list输出，哈哈哈，发现居然AC了。代码如下：</p><pre><code class="language-python">class Solution:        def writeDict(self, mem, key, value):            print(value)            if key in mem:                mem[key].append(value)            else:                mem[key] = [value]                    def fourSum(self, nums, target):        &quot;&quot;&quot;        :type nums: List[int]        :type target: int        :rtype: List[List[int]]        &quot;&quot;&quot;        mem = dict()        nums.sort()        res = []                for prior1_idx in range(len(nums)-2):            for prior2_idx in range(prior1_idx + 1, len(nums)-1):                sum_prior = nums[prior1_idx] + nums[prior2_idx]                prior = (nums[prior1_idx], nums[prior2_idx])                if sum_prior in mem and prior in mem[sum_prior]:                    continue                                    sum_post = target - sum_prior                temp = nums.copy()                temp.remove(nums[prior1_idx])                temp.remove(nums[prior2_idx])                l, r = 0, len(temp) - 1                while l &lt; r:                    if temp[l] + temp[r] &lt; sum_post:                        l += 1                    elif temp[l] + temp[r] &gt; sum_post:                        r -= 1                    else:                        post = (temp[l], temp[r])                        if sum_post in mem and post in mem[sum_prior]:                            l += 1                            r -= 1                            continue                        else:                            # 写入字典                            self.writeDict(mem, sum_prior, prior)                            self.writeDict(mem, sum_post, post)                            single = list(prior) + list(post)                            single.sort()                            res.append(single)                            l += 1                            r -= 1                 return list(set(tuple(i) for i in res))</code></pre><p>很长，很不好用，但是能过，好了不耍流氓了。看了一些大牛的解法，终于是时候推出普适的解法了。<br>先贴一下代码：</p><pre><code class="language-python">class Solution:    # @return a list of lists of length 4, [[val1,val2,val3,val4]]    def fourSum(self, num, target):        num.sort()        def ksum(num, k, target):            i = 0            result = set()            if k == 2:                j = len(num) - 1                while i &lt; j:                    if num[i] + num[j] == target:                        result.add((num[i], num[j]))                        i += 1                    elif num[i] + num[j] &gt; target:                        j -= 1                    else:                        i += 1            else:                while i &lt; len(num) - k + 1:                    newtarget = target - num[i]                    subresult = ksum(num[i+1:], k - 1, newtarget)                    if subresult:                        result = result | set( (num[i],) + nr for nr in subresult)                    i += 1                            return result                return [list(t) for t in ksum(num, 4, target)]</code></pre><p>这是我看下来写得比较清楚的一种，运用了递归，最小子问题就是一个普通的2sum双指针解法，在else里面，将外层的指针直到的数与target作差作为下一个k-1 sum的子任务目标值，进行递归求解。</p><pre><code class="language-python">result = result | set( (num[i],) + nr for nr in subresult)</code></pre><p>这一行代码是关键，<code>(num[i],)</code>是tuple特有的形式，将之后满足的解灌入这个tuple中，外层用一个set包裹来达到去重的目的，因为双指针是依赖于排过序的。最后用<code>result|</code>来对所有的set取并集得到一个set of tuples的结果。最终，再把结果转化为list of lists就结束了。</p><p>这个办法，我觉得可能可以记忆一下，尤其是递归的思想和对结果的转化，真的不是短时间可以想到的，这里的2个模板，一个是基本的2sum，一个是ksum的转化，可以做一下记忆。(๑´ㅂ`๑)</p><h2 id="Leetcode-77-Combination-组合">Leetcode-77 Combination 组合</h2><blockquote><p>给定两个整数 n 和 k，返回 1 … n 中所有可能的 k 个数的组合。</p></blockquote><blockquote><p>示例:<br>输入: n = 4, k = 2<br>输出:<br>[<br>[2,4],<br>[3,4],<br>[2,3],<br>[1,2],<br>[1,3],<br>[1,4],<br>]</p></blockquote><p>组合题，在第二周的记录上，曾经做过一个permutation的题，这两个题一般对照来看的，唯一区别在于，permutation可以重复，combination不可以重复。当时用dfs和backtracking都做过一遍，combination也用两种来处理一下。</p><p>首先是DFS，相对来说简单明了。模板就是for（或者while）循环里套上递归，进入递归函数第一步先判断是否满足条件，满足就插入到结果中，不满足继续递归。注意这里递归要加1，因为这是combination，不加一就是permutation了，不信你可以试试。代码如下：</p><pre><code class="language-python">class Solution(object):    def combine(self, n, k):        &quot;&quot;&quot;        :type n: int        :type k: int        :rtype: List[List[int]]        &quot;&quot;&quot;        res = []                def dfs(array, k, path):            if k == 0:                res.append(path)                return             else:                for i in range(len(array)):                    dfs(array[i+1:], k-1, path + [array[i]])        dfs(range(1, n+1), k, [])        return res</code></pre><p>回溯法，等于是先构建了一棵很大的树，可以认为是贪心遍历所有结构了。用path记录贪心走过的所有路程，一旦走到结束或者遍历不满足，则回退，这里我们使用数组pop来回退。这里有一个加速的剪枝技巧，就是我们另外还需要的个数已经大于数组中可以取到的个数，那就直接return。代码如下：</p><pre><code class="language-python">class Solution(object):    def combine(self, n, k):        &quot;&quot;&quot;        :type n: int        :type k: int        :rtype: List[List[int]]        &quot;&quot;&quot;        res = []        if k==0 or n&lt;1:            return res                def backtracking(n, k, path, index):            if len(path) == k:                res.append(path[:])                return                        if k-len(path) &gt; n - index + 1:                 return                        for num in range(index, n+1):                if num not in path:                    path += [num]                            backtracking(n, k, path, num+1)                    path.pop()        backtracking(n, k, [], 1)        return res</code></pre><p>两种方式都不错，而且可以当做模板来解决一堆类似的问题。相比较而言，dfs更符合人的理解，回溯这个pop还是需要习惯一下，不过也因熟练程度而异。</p><h2 id="Leetcode-39-组合之和">Leetcode-39 组合之和</h2><blockquote><p>给定一个无重复元素的数组 candidates 和一个目标数 target ，找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。</p></blockquote><blockquote><p>说明：<br>所有数字（包括 target）都是正整数。<br>解集不能包含重复的组合。<br>示例 1:<br>输入: candidates = [2,3,6,7], target = 7,<br>所求解集为:<br>[<br>[7],<br>[2,2,3]<br>]<br>示例 2:<br>输入: candidates = [2,3,5], target = 8,<br>所求解集为:<br>[<br>[2,2,2,2],<br>[2,3,3],<br>[3,5]<br>]</p></blockquote><p>分析题目，给定目标值和一个数组，对于最终取得的元素重复没有做限制。因此采用DFS剪枝是我觉得比较好的方式，逻辑上实现比较清楚。构建dfs函数，3个参数，传入的array数组，没进行一次运算之后剩余要达到的target，以及记录累计的结果存入path中。2个截止条件，如果gap为0了，加入到结果中；如果gap小于当前数组中最小的元素了，说明不可能满足了，直接return回退。</p><p>这里对原始数组进行排序，可以达到剪枝的目的（一旦当前数字已经大于gap了，就直接return，不用再比较余下的数字了）。</p><p>算比较清楚，代码如下：</p><pre><code class="language-python">class Solution:    def combinationSum(self, candidates, target):        &quot;&quot;&quot;        :type candidates: List[int]        :type target: int        :rtype: List[List[int]]        &quot;&quot;&quot;        res = []        candidates.sort()        # path记录遍历过的数字        def dfs(array, gap, path):            if gap == 0:                res.append(path)                return                        if gap &lt; min(array):                return                         for index, item in enumerate(array):                dfs(array[index:], gap - item, path + [item])                        dfs(candidates, target, [])        return res</code></pre><h2 id="Leetcode-40-Combination-Sum-III-组合之和2">Leetcode-40 Combination Sum III 组合之和2</h2><blockquote><p>给定一个数组 candidates 和一个目标数 target ，找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。</p></blockquote><blockquote><p>说明：<br>所有数字（包括目标数）都是正整数。<br>解集不能包含重复的组合。</p></blockquote><blockquote><p>示例 1:<br>输入: candidates = [10,1,2,7,6,1,5], target = 8,<br>所求解集为:<br>[<br>[1, 7],<br>[1, 2, 5],<br>[2, 6],<br>[1, 1, 6]<br>]</p></blockquote><blockquote><p>示例 2:<br>输入: candidates = [2,5,2,1,2], target = 5,<br>所求解集为:<br>[<br>[1,2,2],<br>[5]<br>]</p></blockquote><p>和之前的题目类似，但是要解决一个题目条件里的重复问题，我先想了一个比较tricky的办法，就是用set of tuple来解决重复问题，其余和77题是一样的。代码如下：</p><pre><code class="language-python">class Solution:    def combinationSum2(self, candidates, target):        &quot;&quot;&quot;        :type candidates: List[int]        :type target: int        :rtype: List[List[int]]        &quot;&quot;&quot;        candidates.sort()        res = []                def dfs(array, gap, path):            if gap == 0:                res.append(path)                return                        if len(array) &gt;0 and gap &lt; array[0]:                return                        for index, item in enumerate(array):                dfs(array[index+1:], gap - item, path + [item])                        dfs(candidates, target, [])        return [list(tupl) for tupl in {tuple(item) for item in res }]</code></pre><p>可以AC，但是很丑陋。</p><p>唯一的区别在于如何处理重复，这里的重复比较讲究，因为要避免[1, 1, 5]也被归入重复的组合中，所以如何区分内层重复和外层重复就比较重要。举个例子来看，比如[10,1,2,7,6,1,5]，排好序是[1, 1, 2, 5, 6, 7, 10]，这里采用dfs就好比张开了一棵树：<br><a href="https://postimg.cc/BPDT574k" target="_blank" rel="noopener"><img src="https://i.postimg.cc/prcsH37W/image.png" alt="image.png"></a><br>我们需要避免的是红色框，同级之间的重复，而对于跨层的重复是不需要处理的。因此index需要大于start，以保证我们是向后来检查同级是否有后续的数字有和当前重复的。这块其实不容易想，我花了好久时间才想明白，代码如下：</p><pre><code class="language-python">class Solution:    def combinationSum2(self, candidates, target):        &quot;&quot;&quot;        :type candidates: List[int]        :type target: int        :rtype: List[List[int]]        &quot;&quot;&quot;        candidates.sort()        res = []                def dfs(array, start, target, path):            if target == 0:                res.append(path)                return             if target &lt; 0:                return             for index in range(start, len(array)):                if index &gt; start and array[index] == array[index-1]:                    continue                dfs(array, index+1, target - array[index], path + [array[index]])                        dfs(candidates, 0, target, [])        return res</code></pre><h2 id="Leetcode-216-Combination-Sum-III-组合之和3">Leetcode-216 Combination Sum III 组合之和3</h2><blockquote><p>找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数，并且每种组合中不存在重复的数字。</p></blockquote><blockquote><p>说明：<br>所有数字都是正整数。<br>解集不能包含重复的组合。</p></blockquote><blockquote><p>示例 1:<br>输入: k = 3, n = 7<br>输出: [[1,2,4]]</p></blockquote><blockquote><p>示例 2:<br>输入: k = 3, n = 9<br>输出: [[1,2,6], [1,3,5], [2,3,4]]</p></blockquote><p>这题基本和39题是一毛一样的，也没什么好讲的，两个条件，k的个数要等于0，且和等于target，同事满足即可，直接贴代码了。</p><pre><code class="language-python">class Solution(object):    def combinationSum3(self, k, n):        &quot;&quot;&quot;        :type k: int        :type n: int        :rtype: List[List[int]]        &quot;&quot;&quot;        nums = list(range(1, 10))        res = []                def dfs(array, target, path, k):            if target == 0 and k == 0:                res.append(path)                return            if target &lt; 0 or k &lt; 0:                return                         for index in range(len(array)):                dfs(array[index+1:], target - array[index], path + [array[index]], k-1)        dfs(nums, n, [], k)        return res</code></pre><h2 id="Leetcode-328-奇偶链表">Leetcode-328 奇偶链表</h2><blockquote><p>给定一个单链表，把所有的奇数节点和偶数节点分别排在一起。请注意，这里的奇数节点和偶数节点指的是节点编号的奇偶性，而不是节点的值的奇偶性。</p></blockquote><blockquote><p>请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1)，时间复杂度应为 O(nodes)，nodes 为节点总数。</p></blockquote><blockquote><p>示例 1:<br>输入: 1-&gt;2-&gt;3-&gt;4-&gt;5-&gt;NULL<br>输出: 1-&gt;3-&gt;5-&gt;2-&gt;4-&gt;NULL</p></blockquote><blockquote><p>示例 2:<br>输入: 2-&gt;1-&gt;3-&gt;5-&gt;6-&gt;4-&gt;7-&gt;NULL<br>输出: 2-&gt;3-&gt;6-&gt;7-&gt;1-&gt;5-&gt;4-&gt;NULL</p></blockquote><p>我发现我一直不是很会做链表题，感觉可能少根筋，不过这道题还可以，题目意思是要将奇数位数字的node相连，将偶数位node相连，再拼接2个链表。</p><p>一般采取的方式是，dummy两个新的链表，将oddhead定义为奇数位头，evenhead定义为偶数位头，，再定义两个链表分别移动的指针为odd和even，因为是奇数和偶数位连接，所以要跳开一位，等2个链表内部串联好后，将odd的最后一个node连接上even链表，整个过程就完工啦，代码如下：</p><pre><code class="language-python"># Definition for singly-linked list.# class ListNode(object):#     def __init__(self, x):#         self.val = x#         self.next = Noneclass Solution(object):    def oddEvenList(self, head):        &quot;&quot;&quot;        :type head: ListNode        :rtype: ListNode        &quot;&quot;&quot;        if head is None:            return None                odd=oddhead=head        even=evenhead=head.next                while even and even.next:            odd.next=odd.next.next            odd=odd.next            even.next=even.next.next            even=even.next                odd.next=evenhead        return oddhead</code></pre><p>这里dummy两个新的链表，再分别用各自的头来遍历是链表题常用的技巧，链表的循环条件是next指针是null，因此也常用来练习while啦。</p><h2 id="Leetcode-322-找零钱-coin-change">Leetcode-322 找零钱 coin change</h2><blockquote><p>给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额，返回 -1。</p></blockquote><blockquote><p>示例 1:<br>输入: coins = [1, 2, 5], amount = 11<br>输出: 3<br>解释: 11 = 5 + 5 + 1</p></blockquote><blockquote><p>示例 2:<br>输入: coins = [2], amount = 3<br>输出: -1</p></blockquote><p>一看要求最优解，就知道一定是一道动态规划的问题，这里在满足面值和的情况下，还要coin的数量最小，如果不满足的话，还要输出1。其实这个递推公式并不是特别的难写，我们当前的最优解，是由上一个的最优解加上1枚硬币得到的，因此就可以无限递推下去，列出递推公式如下：</p><p>$$<br>F ( S ) = \left{ \begin{array} { l l } { 0 , } &amp; { \text { if } S = 0 } \ { \min _ { i = 0 , \cdots , n - 1 } F \left( S - c _ { i } \right) + 1 , } &amp; { \text { if } S - c _ { i } \geq 0 } \ { - 1 } &amp; { \text { other } } \end{array} \right.<br>$$</p><p>大致演算了一下：</p><pre><code class="language-python">dp[0] = 0dp[1] = 1dp[2] = min{dp[2-1]}+1dp[3] = min{dp[3-1],dp[3-2]}+1dp[4] = min{dp[4-1],dp[4-2]}+1...dp[11] = min{dp[11-1],dp[11-2],dp[11-5]}+1</code></pre><p>这里需要额外注意的是，如果不存在的话，需要返回-1，因此在初始化的时候，将数组中的值全部化为正无穷inf，方便最后取不到的时候结果输出，当金额为0时即dp[0]=0，因为金额为0什么都不取。代码如下：</p><pre><code class="language-python">class Solution(object):    def coinChange(self, coins, amount):        &quot;&quot;&quot;        :type coins: List[int]        :type amount: int        :rtype: int        &quot;&quot;&quot;        n = len(coins)        # dp[i]表示amount=i需要的最少coin数        dp = [float(&quot;inf&quot;)] * (amount+1)        dp[0] = 0        for i in range(amount+1):            for j in range(n):                # 只有当硬币面额不大于要求面额数时，才能取该硬币                if coins[j] &lt;= i:                    dp[i] = min(dp[i], dp[i-coins[j]]+1)        # 硬币数不会超过要求总面额数，如果超过，说明没有方案可凑到目标值        return dp[amount] if dp[amount] &lt;= amount else -1</code></pre><h2 id="Leetcode-146-LRU缓存机制">Leetcode-146 LRU缓存机制</h2><blockquote><p>运用你所掌握的数据结构，设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作： 获取数据 get 和 写入数据 put 。</p></blockquote><blockquote><p>获取数据 get(key) - 如果关键字 (key) 存在于缓存中，则获取关键字的值（总是正数），否则返回 -1。<br>写入数据 put(key, value) - 如果关键字已经存在，则变更其数据值；如果关键字不存在，则插入该组「关键字/值」。当缓存容量达到上限时，它应该在写入新数据之前删除最久未使用的数据值，从而为新的数据值留出空间。<br>进阶:<br>你是否可以在 O(1) 时间复杂度内完成这两种操作？</p></blockquote><p>解题思想：这是一道常考的套路题，需要用到 2 种数据结构来解决问题，双向链表和哈希表；其中双向链表可以更好的定位到前后的元素，而哈希表是记录所有 key 所在的位置。LRU 的意义是最近最少使用，即如果一个页面是最近最少使用的，在到达 LRU 最大距离的时候就要弹出这个页面。具体分为 get 和 put 两个操作：</p><ol><li>get 操作：如果哈希表中有这个 key，则返回这个 key 的值，并且由于是最近使用的，所以需要将其移动至双向链表的头部；没有则返回-1</li><li>put 操作：将（key，value）的一个 pair 加入到双向链表中，这边就存在 2 种情况：如果有则直接修改值，并且移动至链表头部；如果没有值，则新建链表节点，移动至头部，并且需要检查总的长度是否超过 LRU 总的容量，超过则要删除链表中最后一个节点，并且在哈希表里也要弹出这个节点对应的 key。</li></ol><p>这块代码其实不难，但是需要自己新建双向链表，以及链表中删除移动等操作，核心考察的是数据结构的运用，贴一下代码：</p><pre><code class="language-python">class DLinkedNode:    def __init__(self, key=0, value=0):        self.key = key        self.value = value        self.prev = None        self.next = Noneclass LRUCache:    def __init__(self, capacity: int):        self.cache = {}        self.capacity = capacity        self.size = 0        self.head = DLinkedNode()        self.tail = DLinkedNode()        self.head.next = self.tail        self.tail.prev = self.head    def addToHead(self, node):        node.next = self.head.next        node.prev = self.head        self.head.next = node        node.next.prev = node    def removeNode(self, node):        print(node.key)        print(node.value)        print(node.next)        node.prev.next = node.next        node.next.prev = node.prev    def moveToHead(self, node):        self.removeNode(node)        self.addToHead(node)    def removeTail(self):        node = self.tail.prev        self.removeNode(node)        return node    def get(self, key: int) -&gt; int:        if key not in self.cache:            return -1        # 如果 key 存在，先通过哈希表定位，再移到头部        node = self.cache[key]        self.moveToHead(node)        return node.value    def put(self, key: int, value: int) -&gt; None:        if key in self.cache:            node = self.cache[key]            node.value = value            # move to the header            self.moveToHead(node)        else:            node = DLinkedNode(key, value)            self.cache[key] = node            # move to the header            self.addToHead(node)            self.size += 1            if self.size &gt; self.capacity:                # remove the tail                removed = self.removeTail()                self.cache.pop(removed.key)                self.size -= 1</code></pre>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;Leetcode-70-爬楼梯问题（DP）&quot;&gt;Leetcode-70 爬楼梯问题（DP）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;You are climbing a stair case. It takes n steps to reach to the top.&lt;br&gt;
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?&lt;br&gt;
Note: Given n will be a positive integer.&lt;br&gt;
Input: 2&lt;br&gt;
Output: 2&lt;br&gt;
Explanation: There are two ways to climb to the top.&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="算法" scheme="http://alexjiangzy.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="Leetcode" scheme="http://alexjiangzy.com/tags/Leetcode/"/>
    
  </entry>
  
  <entry>
    <title>Data analytics about the Movie Industry using R and ggplot from IMDB-5000</title>
    <link href="http://alexjiangzy.com/2018/04/07/R-imdb5000/"/>
    <id>http://alexjiangzy.com/2018/04/07/R-imdb5000/</id>
    <published>2018-04-07T01:22:05.000Z</published>
    <updated>2018-04-11T06:05:28.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/imdb5000/imdb.jpg" alt=""></p><h3 id="写在前面">写在前面</h3><p>在NUS的学习过程中所涉及到的一些Assignments，会陆陆续续整理然后放上来，由于是英语书写同时也为了锻炼自己的英文写作，遂会尽量用英文来写整个的过程（其实也是照抄作业而已）。本Assignment是关于IMDB5000电影评分的一个分析，比较偏入门的一个作业，通过这个作业可以对R中重要的可视化作图模块有一个较为整体的认识。<br></p><h3 id="Introduction">Introduction</h3><p>IMDB 5000 is an Internet movie database with highest rated TV series with at least 5000 votes. In this task, IMDB 5000 is implemented as our database to explore and visualize the different distributions depending on various factors. This dataset could be found in data.world or Kaggle dataset, which includes 28 factors and 5043 movies. The metadata is listed below:</p><table><thead><tr><th style="text-align:left">Variables</th><th style="text-align:center">Variables</th><th style="text-align:center">Variables</th><th style="text-align:right">Variables</th></tr></thead><tbody><tr><td style="text-align:left">color</td><td style="text-align:center">actor_2_facebook_likes</td><td style="text-align:center">actor_3_name</td><td style="text-align:right">budget</td></tr><tr><td style="text-align:left">director_name</td><td style="text-align:center">movie_facebook_likes</td><td style="text-align:center">facenumber_in_poster</td><td style="text-align:right">title_year</td></tr><tr><td style="text-align:left">num_critic_for_reviews</td><td style="text-align:center">gross</td><td style="text-align:center">imdb_score</td><td style="text-align:right">aspect_ratio</td></tr><tr><td style="text-align:left">duration</td><td style="text-align:center">genres</td><td style="text-align:center">plot_keywords</td><td style="text-align:right">content_rating</td></tr><tr><td style="text-align:left">director_facebook_likes</td><td style="text-align:center">actor_1_name</td><td style="text-align:center">movie_imdb_link</td><td style="text-align:right">cast_total_facebook_likes</td></tr><tr><td style="text-align:left">actor_3_facebook_likes</td><td style="text-align:center">movie_title</td><td style="text-align:center">num_user_for_reviews</td><td style="text-align:right">actor_1_facebook_likes</td></tr><tr><td style="text-align:left">actor_2_name</td><td style="text-align:center">num_voted_users</td><td style="text-align:center">language</td><td style="text-align:right">country</td></tr></tbody></table><a id="more"></a><h3 id="Objectives">Objectives</h3><p>The objective of this report is to answer the following questions based on the IMDB 5000 dataset:</p><ul><li>What is the trend for number of movies produced annually, and their<br>corresponding IMDB scores?</li><li>What are the popular movie genres based on IMDB scores?</li><li>What are the national trends for movies and their corresponding quality?</li><li>How does budget and mean gross impact IMDB scores?</li><li>Who are the most prolific directors, while which directors have the highest reputation?</li></ul><h3 id="Data-Clean">Data Clean</h3><p>The dataset was first checked to ensure that there were no missing values. Movie title and IMDB score was treated as essential data in the dataset, and duplicated movies with the same title, excluding the series movies that may have the same title, were removed. After omitting 45 movies, 4998 movies remain.</p><ul><li>It was observed during data cleaning that all movies titles contained the unknown character “Â “. As such, “movie_title” column was formatted to remove this.</li></ul><pre><code class="language-r">splitFun &lt;- function(x) strsplit(x, '\302')[[1]][1]cleaned_titles = sapply(titles, splitFun)</code></pre><h3 id="Visualization">Visualization</h3><p>This part was aimed to answer the questions declared in objectives.</p><h4 id="Question-1-Annual-movies-general-quantity-and-quality">Question 1: Annual movies general quantity and quality</h4><p><strong>What is the trend for number of movies produced annually, and their corresponding IMDB scores?</strong></p><p>The subset of dataset was selected from year of 1980. The movies count versus years was compiled in R. Using geom_text to add the maximum point in the plot.</p><pre><code class="language-r">q1_data_greater_1980 = q1_data[as.numeric(q1_data$title_year) &gt; 1980, ]q1_data %&gt;%  group_by(title_year) %&gt;%  summarise(count_year = n()) %&gt;%  ggplot(aes(x = title_year, y = count_year, fill = count_year)) +   geom_bar(stat = &quot;identity&quot;) +   geom_text(aes(2009, 280, label = &quot;Max = 260&quot; )) +   labs(x=&quot;Movies Year&quot;, y=&quot;Year Count&quot;) +   ggtitle(&quot;Distribution of year for the movies count&quot;) +   scale_fill_gradient(low = &quot;purple&quot;, high = &quot;red&quot;)</code></pre><img src="/images/imdb5000/fig1a.png" width="80%"><p>Furthermore, the median score of annual movies versus different years was plot using ggplot. <strong>geom_smooth()</strong> was used to smooth the tendency line to describe the general relationship.</p><pre><code class="language-r">q1_data %&gt;%  group_by(title_year) %&gt;%  summarise(mean_score = mean(imdb_score)) %&gt;%  ggplot(aes(x = title_year, y = mean_score, group = 1)) +   geom_point() +  geom_line() +  geom_smooth() +   labs(x=&quot;Movies Year&quot;, y=&quot;Mean IMDB score every year&quot;) +   ggtitle(&quot;Mean IMDB score for each year&quot;)</code></pre><img src="/images/imdb5000/fig1b.png" width="80%"><p>Figure 1(a) illustrates the distribution of movies produced annually. It’s noted that before 1980, the overall quantity of movies produced was relatively low. From 1980 to the mid-1990s, we note a gradual increase in the number of movies produced. Since the mid-1990s however, the movie market grew tremendously, reaching a high of 260 in 2009.</p><p>The figure 1(b) illustrates the annual mean IMDB scores points, with the best fit line indicated in blue. From the graph, it’s observed that the mean IMDB score has been increasing before 1950. After 1950 however, the mean IMDB scores has been reducing over the years, reaching an all-time low of 6 in 2015, the lowest since 1960.</p><p>From the above trends, <strong>it’s evident that even though the movie industry has been growing exponentially over the years, these do not correspond with an increase in the mean IMDB scores. This means that there has been a significant number of low quality movies.</strong> Consequently, it’s also observed that people enjoy classical movies from the early years.</p><h4 id="Question-2-Popular-movie-genres">Question 2: Popular movie genres.</h4><p>To answer this question, some additional data cleaning beyond those stated in section 3.1 was done, as there were some genres that were separated by a vertical line (“|”). These genres were split, and the first genre was considered as the primary genre. This left us with 21 genres.</p><pre><code class="language-r">judgeAndSplit &lt;- function(x) {  sp = strsplit(x, '[|]')[[1]][1]  return(sp)}genres_name = as.character(movies$genres) movies = movies %&gt;%  mutate(genres1 = as.factor(sapply(genres_name, judgeAndSplit)))</code></pre><p>Since we’re interested in popular genres, genres with more than 10 movies were selected, resulting in 14 key genres selected. With these genres, a boxplot was generated in Figure 2. Note that these genres were arranged in descending order in terms of their median IMDB scores.</p><pre><code class="language-r">typeLabelCount = q2_modified %&gt;%  group_by(genres1) %&gt;%  summarise(count = n()) %&gt;%  as.data.frame()q2_modified %&gt;%  ggplot(aes(reorder(genres1, imdb_score, median, order = TRUE), y = imdb_score, fill = genres1)) +   geom_boxplot() +   coord_flip() +   geom_label(data = typeLabelCount, aes(x = genres1, y = 10, label = count),  hjust = 0, size = 3) +   ggtitle(&quot;Ordered imdb scores distribution by popular movie genres&quot;) +   guides(fill=FALSE) +   ylim(0, 11) +  labs(x = &quot;Popular movie genre&quot;, y = &quot;IMDB score&quot;)</code></pre><p>From the figure 2 below, the most popular genre was <strong>“Documentary”</strong>, achieving a median score nearly 7 with 84 movies. The least popular genre out of the 14 key genres selected was <strong>“Thriller”</strong>, with a median score of around 5 out of of 21 movies. While the “Comedy” and “Action” genres had the largest numbers, the overall IMDB score performances were still poor.</p><p>As such, we conclude that genres such as <strong>“Documentary”, “Biography”, “Western”, “Crime” and “Drama”</strong>, which are adapted either from reality or original novels, tend to perform have higher scores.<br><img src="/images/imdb5000/fig2.png" width="80%"></p><h4 id="Question-3-National-movies-quality">Question 3: National movies quality</h4><p>In this part, the top 20 countries that produced the most movies were considered, thus demostrated the IMDB scores condition.</p><pre><code class="language-r">q3_data[q3_data$country %in% countriesTopList$country, ] %&gt;%  ggplot(aes(reorder(country, imdb_score, median, order = TRUE), imdb_score, fill = country)) +  geom_boxplot() +   coord_flip() +   guides(fill=FALSE) +  geom_label(data = as.data.frame(q3_top20_cny), aes(x = country, y = 10, label = country_count), hjust = 0, size = 2.5) +  labs(x = &quot;Country&quot;, y = &quot;IMDB score&quot;, title = &quot;Top 20 countries IMDB scores&quot;) +  ylim(0, 11)</code></pre><p>From Figure 3, we observe that although <strong>USA</strong> had the highest movie quantity (3773) and took up more than 75% of the dataset, it only achieved a rank of 16th in the IMDB median scores ranking. If we considered the quality of the movies produced, the country with the highest median score is <strong>Brazil</strong>, and the lowest is <strong>Russia</strong>. If we consider both quantity and quality of movies produced, movies from the <strong>UK movies</strong> had a comprehensively good performance (2nd in quantity, 4th in median scores ranking) amongst all the countries.<br><img src="/images/imdb5000/fig3.png" width="80%"></p><h4 id="Question-4-Budget-and-mean-gross-impact-IMDB-scores">Question 4: Budget and mean gross impact IMDB scores</h4><p>To explore the effect of budget and mean gross to IMDB scores, a new factor named “IMDB score level” was defined, with 0.5 as the score step. This serves to round the IMDB scores to the nearest 0.5. Based on this, the movies’ mean budget and gross belonging to corresponding score levels were calculated. The results are as plotted in a line chart shown in Figure 4 (a) and (b).<br><img src="/images/imdb5000/fig4.png" width="80%"><br>From figure 4 (a) and (b), <strong>we conclude that IMDB scores are positively correlated to budget and gross. Furthermore, movies with a higher score on IMDB tend to do better at the box office. If a movie is expected to have a high score, more financial support should be available in the early stage.</strong></p><pre><code class="language-r">q4_data = moviesq4_cleaned_data = q4_data %&gt;%  subset(!is.na(gross)) %&gt;%  subset(!is.na(budget)) q4_cleaned_data %&gt;%  group_by(imdb_score_level) %&gt;%  summarise(score_count = n(),             mean_budget = mean(budget, na.rm = TRUE)) %&gt;%  ggplot() +  geom_point(aes(x = as.factor(imdb_score_level), y = mean_budget)) +  geom_path(aes(x = as.factor(imdb_score_level), y = mean_budget, group = 1), size = 1, color = 4) +  labs(x = &quot;IMDB score level&quot;, y = &quot;Each score level mean budget(US Dollar)&quot;, title = &quot;Different IMDB score level's mean budget&quot;)q4_cleaned_data %&gt;%  group_by(imdb_score_level) %&gt;%  summarise(score_count = n(),             mean_gross = mean(gross, na.rm = TRUE)) %&gt;%  ggplot() +  geom_point(aes(x = as.factor(imdb_score_level), y = mean_gross)) +  geom_path(aes(x = as.factor(imdb_score_level), y = mean_gross, group = 1), size = 1, color = 6) +  labs(x = &quot;IMDB score level&quot;, y = &quot;Each score level mean gross(US Dollar)&quot;, title = &quot;Different IMDB score level's mean gross&quot;)</code></pre><h4 id="Question-5-Prolific-directors-with-high-quality">Question 5: Prolific directors with high quality?</h4><p>To find out the most prolific directors and high reputation directors, a method similar to Question 3 was used as there are 2399 directors, with many of them holding just 1 movie. As such, the first step was to rank the directors who directed more than or equal to 10 movies, ordered by the movie quantity, shown in figure 5 (a) below.<br><img src="/images/imdb5000/fig5a.png" width="80%"><br>The figure 5(a) above illustrates that the most prolific director is <strong>Steven Spielberg</strong>☺️ who have directed 26 movies, the other prolific directors included <strong>Woody Allen, Martin Scorsese and Clint Eastwood</strong> who all directed over 20 movies.</p><p>From these 35 directors, a boxplot of them and the IMDB score of their movies were generated, and ranked in descending order by their medians of IMDB scores in figure 5 (b).<br><img src="/images/imdb5000/fig5b.png" width="80%"><br>The figure 5(b) above illustrates that the director with highest score is <strong>David Fincher</strong>, having directed 5 movies with a median IMDB score of 7.8. If we consider both quantity and quality of movies produced, 👍<strong>Steven Spielberg</strong> is the director that performs well in both respects (1st in quantity, 2nd in score ranking).</p><pre><code class="language-r"># Q5: Director's impact of the imdb scoresq5_data = moviesq5_data %&gt;%  subset(director_name != &quot;&quot;) %&gt;%  group_by(director_name) %&gt;%  summarise(director_movies_count = n()) %&gt;%  filter(director_movies_count &gt;= 10) %&gt;%  ggplot(aes(x = reorder(director_name, director_movies_count), y = director_movies_count)) +   geom_bar(stat = &quot;identity&quot;, aes(fill = as.factor(director_movies_count))) +  coord_flip() +   guides(fill=guide_legend(title=&quot;Movies\nQuantity&quot;)) +  labs(x = &quot;Director Name&quot;, y = &quot;Movies number&quot;, title = &quot;Director movies number ranking&quot;)topDirectors = q5_data %&gt;%  subset(director_name != &quot;&quot;) %&gt;%  group_by(director_name) %&gt;%  summarise(director_movies_count = n()) %&gt;%  filter(director_movies_count &gt;= 10)directorList = lapply(topDirectors[1], as.factor) q5_data[q5_data$director_name %in% directorList$director_name, ] %&gt;%  ggplot(aes(reorder(director_name, imdb_score, median, order = TRUE), y = imdb_score, fill = director_name)) +   geom_boxplot() +   guides(fill=FALSE) +   coord_flip() +   labs(x = &quot;Director Name&quot;, y = &quot;IMDB score&quot;, title = &quot;Director IMDB score ranking&quot;)</code></pre><h3 id="Conclusion">Conclusion</h3><p>Through visualization and analytics of IMDB 5000 dataset, we focus on 5 issues related to the IMDB scores. During exploration, we emphasize the factors including years (1916 to 2016), 21 types of genres, 66 countries (especially top 20 countries), gross, budget and 35 prolific directors across 5043 movies. After that, some perspectives were made based on data analytics, which were meaningful to summarize and predict the overall movie market tendency. At the same time, the inferences were also beneficial for movie workers to grab audience’s preference effectively.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/imdb5000/imdb.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;
&lt;p&gt;在NUS的学习过程中所涉及到的一些Assignments，会陆陆续续整理然后放上来，由于是英语书写同时也为了锻炼自己的英文写作，遂会尽量用英文来写整个的过程（其实也是照抄作业而已）。本Assignment是关于IMDB5000电影评分的一个分析，比较偏入门的一个作业，通过这个作业可以对R中重要的可视化作图模块有一个较为整体的认识。&lt;br&gt;&lt;/p&gt;
&lt;h3 id=&quot;Introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;IMDB 5000 is an Internet movie database with highest rated TV series with at least 5000 votes. In this task, IMDB 5000 is implemented as our database to explore and visualize the different distributions depending on various factors. This dataset could be found in data.world or Kaggle dataset, which includes 28 factors and 5043 movies. The metadata is listed below:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Variables&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Variables&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Variables&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Variables&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;color&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;actor_2_facebook_likes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;actor_3_name&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;director_name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;movie_facebook_likes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;facenumber_in_poster&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;title_year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;num_critic_for_reviews&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gross&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;imdb_score&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;aspect_ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;duration&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;genres&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;plot_keywords&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;content_rating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;director_facebook_likes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;actor_1_name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;movie_imdb_link&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;cast_total_facebook_likes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;actor_3_facebook_likes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;movie_title&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;num_user_for_reviews&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;actor_1_facebook_likes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;actor_2_name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;num_voted_users&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;language&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;country&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
    
    </summary>
    
      <category term="Data Analytics" scheme="http://alexjiangzy.com/categories/Data-Analytics/"/>
    
    
      <category term="R, Visualization" scheme="http://alexjiangzy.com/tags/R-Visualization/"/>
    
  </entry>
  
  <entry>
    <title>来新杂感</title>
    <link href="http://alexjiangzy.com/2018/03/21/sgFeeling/"/>
    <id>http://alexjiangzy.com/2018/03/21/sgFeeling/</id>
    <published>2018-03-21T15:31:08.000Z</published>
    <updated>2018-12-30T12:18:17.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/sgMarina.jpg" alt=""></p><h2 id="交代">交代</h2><p>距离上一次写博客已经过去2年多了，期间有过好几次想重新开启的时刻，也都随着自己角色的转变以及没救的拖延和懈怠中流逝了。2年前的现在，我还在重庆忙着自己的毕业论文答辩，在一场一场的火锅和KTV中肆意挥洒最后的青春。研究生毕业之后，一个人来到魔都张江，开始了一个真正意义上的程序员的生活，并且也算以此为自己的职业，能在上海养活自己，并且还养的不错，从体重不断的飙升中可见一斑，期间有机会去韩国出差，算是第一次踏出国门。2018年年初又一次下南洋，开始了在国大的另一段求学的生涯，如今坐在NUS的自习室里，云淡风轻的回忆这两年发生的不多不少的种种，有些值得骄傲，有些也感到无力。但这2年总体而言，都是向着好的方向在前进，尤其完成了自己多年以来的出国梦，可能我这一辈子余下的时光都庸庸碌碌，唯有这件事能称之为闪光点吧。</p><a id="more"></a><blockquote><p>人要多打碎自己，要跳出自己的舒适圈</p></blockquote><p>这句话说出来很容易，而且似乎说出口能让人对你高看好多眼。但是真正要去实践这句话，以及说服自己，实则没有那么的简单，尤其是在外人看来，你似乎学历足矣，年纪不小，工作尚可，实在是找不到一个完美的动机去重新读书，毕竟留学回来，可能也还是依旧的写屌丝代码，依旧买不起房，已经突破不了阶层的天花板。拿着一张国内研究生学位，招摇撞骗也差强人意，国内研究生本来就水，更何况自己又心安理得的在一个很一般的学校里度过了3年研究生生活，出来被很多科班计算机本科生吊打也是常事了，一把年纪了，真是又老又挫。</p><p>但是，这些年的很多经历，却让我能更加正面的去认识自己，特别在工作后，自己对于<strong>经历</strong>和<strong>积累</strong>更加看重。我也慢慢意识到，如果能去经历一下，不管结局如何一定都是值得的 (Hypothesis)。过往的经历告诉我，凡是能跳出现有的状况去获得的经历，都能够给予自己很大的加成 (Validation)。何况现在这个时间点不去做这件事，过了30岁似乎只会更加困难 (Condition)。</p><p>笃定了自己的目标，明了了得失，似乎心态也平衡了不少，既没有很强的得失心，也没有很强硬的目的性，结果也就没有太多意外。过程的话，除了准备雅思那段时间，白天工作，晚上复习，也没有太累的时候了，谢谢女朋友在雅思准备上给予了巨大的帮助，谢谢同事和领导的理解和肯定。</p><h2 id="落地">落地</h2><p>新加坡，北纬1度的花园国家，一个常年夏天的国家，一个东西跨度不过50公里的国家（和国内一个县城差不多大），一个七成是华人的国家，一个靠左行驶的国家，一个被英国，日本，马来西亚轮番殖民的国家，一个说着一口Singlish的国家，生活成本全世界第一的国家，政府高压政策下的国家，从前的亚洲四小龙现在经济也不太稳定的国家，东西很好吃的国家，一个多种文化杂糅一起的国家。算是满足了之前对它的各种幻想，没有特别失望的点。<br>一个地方怎么样，很大程度上也取决于人怎么样，有几点可能特别值得称道：</p><ul><li><p>懂得感恩<br>奇葩说陈铭说过，”应该的“这三个字是所有施予善意最大的天敌。所幸在新加坡不会有这个问题，不管是帮人按住电梯，还是给人让个路，都会听到一句高声的“Thank you”，冒犯或打扰到别人，也自然有一句“Sorry”。很简单的不起眼的习惯，给了陌生人最大程度上的好感，却真的能推己及人，让我也深受感染。懂得感恩，就算是陌生人，也要让他知道他的善举有被你接收到。</p></li><li><p>尊重一切<br>做assignment的时候，新加坡同学一定会照顾到团队里每一个人的感想，每一个人都有平等发表意见的权利，可能有时候觉得有点傻没必要，但是换位思考的话，没有尊重也很难体会到他人的情绪，矛盾也就在无形中积累了。刚来新加坡会看到很多残障人士坐着电动轮椅出行，公交司机会下车推行他们上巴士，一般HDB都配有给轮椅用的斜坡，残障人士出行无碍，盲道铺设一丝不苟，真正惠及全民每一个人，没有任何表面工程。</p></li><li><p>保有传统<br>我住在Clementi的老HDB里，经常感觉像是回到了2000年左右的样子，新加坡人习惯在食阁吃东西，食阁感觉就像是以前家乐福底下员工食堂那种感觉。楼下会有很多国内小区住宅里见不到的，关公或者观音烛台贡位，很多裁缝店改衣店，楼下甚至还有个服装学院，小区里很诡异的开了4家卖观赏鱼店，生意还都不错，风水店也是遍地。还有一个我无法理解的，新加坡人喜欢买彩票，并且排队长到一个夸张。</p></li></ul><p>其实还有很多好玩的感悟，相比国内，这里没有遍地的移动支付，大家还是现金加信用卡，没有高铁，聊天软件WhatsApp也经常被我们吐槽性能和微信差远了，但是也没有雾霾，没有转基因，没有很多食品安全问题。</p><p>没有冬天。。。</p><p><img src="/images/nusLogo.jpg" alt=""></p><h2 id="留学">留学</h2><p>关于留学，我只想说比我想象的要累，赶assignment的时候，基本天天半夜2点以后回家的节奏，不免担心自己会不会就这么挂了。自己还有一点language barrier，总之好像英文也没有进步太多，毕竟都是华人说的机会也不是很多。感恩身边不少nice的local给了我很多帮助。</p><p>不过，这段时间算是真的意识到了，学习还真是要靠自己，一个人面对问题时思考的量和深度直接决定了解决问题到什么程度，感恩以前在公司经常和大家讨论和设计，对于逻辑的分析有了很多的训练。另一个，就是想清楚了自己想要从事的领域和努力的方向，明确了自己还是想做技术相关的工种，会去往data scientist的方向努力。剩下的就要看老天指路啦。</p><blockquote><p>向前跑 迎着冷眼和嘲笑<br>生命的广阔不历经磨难怎能感到<br>命运它无法让我们跪地求饶<br>就算鲜血洒满了怀抱<br>继续跑 带着赤子的骄傲<br>生命的闪耀不坚持到底怎能看到<br>与其苟延残喘不如纵情燃烧吧<br>为了心中的美好<br>不妥协直到变老 – GALA《追梦赤子心》</p></blockquote><p>先写这么多吧 以后有新的心得再写吧，加油。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/sgMarina.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;交代&quot;&gt;交代&lt;/h2&gt;
&lt;p&gt;距离上一次写博客已经过去2年多了，期间有过好几次想重新开启的时刻，也都随着自己角色的转变以及没救的拖延和懈怠中流逝了。2年前的现在，我还在重庆忙着自己的毕业论文答辩，在一场一场的火锅和KTV中肆意挥洒最后的青春。研究生毕业之后，一个人来到魔都张江，开始了一个真正意义上的程序员的生活，并且也算以此为自己的职业，能在上海养活自己，并且还养的不错，从体重不断的飙升中可见一斑，期间有机会去韩国出差，算是第一次踏出国门。2018年年初又一次下南洋，开始了在国大的另一段求学的生涯，如今坐在NUS的自习室里，云淡风轻的回忆这两年发生的不多不少的种种，有些值得骄傲，有些也感到无力。但这2年总体而言，都是向着好的方向在前进，尤其完成了自己多年以来的出国梦，可能我这一辈子余下的时光都庸庸碌碌，唯有这件事能称之为闪光点吧。&lt;/p&gt;
    
    </summary>
    
      <category term="生活感悟" scheme="http://alexjiangzy.com/categories/%E7%94%9F%E6%B4%BB%E6%84%9F%E6%82%9F/"/>
    
    
  </entry>
  
  <entry>
    <title>我的2015总结和2016的Todo</title>
    <link href="http://alexjiangzy.com/2015/12/31/my2015/"/>
    <id>http://alexjiangzy.com/2015/12/31/my2015/</id>
    <published>2015-12-30T16:26:59.000Z</published>
    <updated>2018-04-03T14:52:09.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/2016pic.jpg" alt=""></p><h3 id="总体">总体</h3><p>2015还是随风而逝了，有点感伤，也有点感动。感伤于很多时候的无所事事，耽误了不少时间。感动在于一整年也算是完成了自己的一系列小目标，看到了自己的成长。而更多的是看到了_<strong>差距</strong>_，差距来源于自己视野的不断打开，正因为如此我会有很多对自己的不满足，不满足于自己的拖沓，不满足于自己的懒散。但总体来说这些都算是人生路上的好事，因为我不可能永远停留着做个学生，这一年我也看到了自己的多面性，因为人这一生也是自我认识的过程。<br><strong>2015年足迹经过了无锡，上海，杭州，重庆，成都，哈尔滨。</strong><br>过去的旧时光我都记得，但是更重要的还是向前看。我还是要对自己的2015做一些总结。<br>2015对我而言的年度词语我觉得应该是——<strong>开眼</strong>。</p><a id="more"></a><h3 id="关键词一：说走就走">关键词一：说走就走</h3><p>2015年初，我说走就走去了趟哈尔滨，拿着在松哥那里实习赚的钱去了那个我一直梦寐以求的冰雪世界。我一直都向往北方那种飒爽，冬日漫天白雪的万丈豪情我懂，冬日漫天白雪的安详静谧我也懂。庄严精致的索非亚大教堂，松花江上的狗拉爬犁，精美的冰雪大世界，风刮的扎脸的亚布力，哈工大里的锅包肉白肉粉条，零下20度泡着热水澡等等等，这些个景致杂揉在一起，要我形容，我也只能对着天大吼一句：<strong>爽！</strong>。这里也要谢谢大鑫哥。<br><img src="/images/harbin.jpg" alt=""><br>2015年末，我再次说走就走去了趟成都，成都我觉得简直不能再赞了。虽说是在重庆上学，但是成都给我的感觉完全不一样。成都有我没有料想到的繁华和现代，不管是IFS还是世纪城金融城，完全可以比肩上海，这个城市早就不是我料想中的崛起之势了，俨然是发达了。另外成都是真的安逸（虽然重庆也安逸，但是tm重庆天气实在不好，心情也就不好了），阳光下去人民公园喝喝茶，再去宽窄巷子听个变脸，晚上整个串串猪脚汤，再泡个澡也是嗨到不行（好像两段经历我都要在泡澡后才觉得爽，哈哈）。这里要谢谢李鑫和小平。<br><img src="/images/chengduIFS.jpg" alt=""><br>可能是从江南小镇走出来的缘故，总觉得无锡这个地方总是差点意思，特别对比上述两个城市之后，现在想来可能还是少了一些霸气，缺了一点大气吧。</p><h3 id="关键词二：带队伍">关键词二：带队伍</h3><p>这部分其实也算是对我在学校的小总结。因为我现在也到了研三这个阶段，说实话我觉得我科研搞的真心一般，也可能导师对我的定位不是那种静下来搞科研的，因此我得到了很多带队伍的机会。因为我还算是比较有责任心吧，所以对待带队伍这个事儿还是蛮当回事儿的。年初哈尔滨回来，立马带着师弟一起去到清华大学物联网中心，合作撰写了一个国家自然科学基金。这也是我第一次能按照自己的想法来规划一个项目，有点产品经理的味道，从前期和清华老师的讨论，到制定本子的大框架，看大量的paper，将技术环节分模块、分流程、分阶段，再到分工总结，画好每一个流程图，最后多轮次的修改。其实现在想想也没什么，后期也有点乏力，但是作为一个制定者，进度安排者，还是需要锻炼下大局观的。同时，清华这个平台也让我结交了几个朋友，也看到了什么叫实力，什么叫牛逼的气场，什么叫正经的坚持（举个小例子，大家都会下几个背单词的软件吧，坐我旁边的伯元也一样，我看到了他扇贝签到是1000多天，背三年多了没断过）。然而最后这个本子没中，但是我看到评审意见还是值得肯定。努力就行了，结果听天命吧。</p><p>研二下学期，导师给了我几个含弘学院和电信院的学生，让我带着她们做项目，然后我又开始扮演了带队伍的角色，然后上的居然是安卓。于是乎我也只能现学现卖，而且是彻底的现学先卖，因为我之前java也没怎么写过。于是我开始着手规划，跟着<a href="www.imooc.com">慕课网</a>闷头看了一段时间java，理解了继承、多态、接口、封装，发现也都差不多，然后买了本Bignerd的<a href="http://www.amazon.cn/Android%E7%BC%96%E7%A8%8B%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97-%E8%8F%B2%E5%88%A9%E6%99%AE%E6%96%AF/dp/B00J4DXWDG/ref=sr_1_4?ie=UTF8&amp;qid=1451640139&amp;sr=8-4&amp;keywords=%E5%AE%89%E5%8D%93" target="_blank" rel="noopener">Android编程权威指南</a>。一节一节看，一章一章讲，一周一次，一边实践，一边也看paper跟上。于是乎，我理解了android的MVC，理解了list-view，理解了多页面通信，理解了传感器调用。因为是带队伍，也带着同学们搭了git环境，手把手讲解了书上的例子，及时做了答疑share了很多我的理解，带着做了高德和调用了sdk实现了定位，也重构了一个别人的app。但是后期却感觉提不起什么精神，因为了解下来大家都有着自己的打算，有的不是纯粹的来学习实践，是盯着能有好方向写paper，出国年master或者docter，有些可能只是应付老师。作为我个人来说，我当然理解这种行为，人不为己，天诛地灭，再者说又有多少人真心喜欢写这屌丝破代码。只能说我喜欢纯粹的学习，纯粹的坚持，当然也有点羡慕他们对未来的规划，我像他们这么大的时候真的毛都不知道。<br>带队伍是辛苦的，但是也是值得的，怎么说呢，每个人要的和选择的不一样，我也不能妄做评断，重新认识自己吧。另外我们组的一个女生也顺利得到了清华的直博，可喜。<br><img src="/images/teacherTime.jpg" alt=""><br>除了带队伍，也中了一篇EI的水文，也算是在研究生阶段有个交代了。其他好像真没啥了。</p><h3 id="关键词三：上海滩">关键词三：上海滩</h3><p>今年收获最大的地方，未来事业开始的地方，也是未来即将为之努力奋斗的地方。</p><p>15年暑假在英语流利说找了一个很给力点实习，这一部分内容拖了很久，因为总是不知道怎么完备的去描述。大概分三块来说吧，公司，人和我做的事儿。</p><h4 id="公司">公司</h4><p>我最早是在拉勾上找的实习，说实话当时没抱多大的希望，觉着自己也没啥干货，找个差不多的公司水一水就差不多了。也许是老天不忍心让我就这么水一水吧，无意间看到个牛叉流利说，谷歌算法团队，湾区办公环境，各种健身房、免费三餐、水果，相当满意的salary。还和赵兴骢吹逼，这谷歌科学家要我我还不起飞了。然后就开始了三次极其相当顺利的电面（虽然中间有点小意外），后来想来也并不是很偶然，因为我之前有写前端的经验，而流利说正好需要一个可视化的平台，所以技能树总体还是比较match的，然后6月底就背着包去了。</p><p>到了流利说，我才知道拉勾上的jd都是事实，公司不大，但是极尽小资，从人员到boss们到公司制度都是完全的逼格满格，典型的小而美的公司。各种人性化的规定，比如实习生免费有屋子住，杨浦这个地价给套免费的房子住，并且还有完全给力的床上用品，奢侈的一比；整个公司的上班时间不打卡，真正做到了完全的弹性工作制，弹的不要不要的，弹的我一般是上午10点到公司都算有点早的；三餐供应，阿姨送饭，说实话，没吃到这么良心的饭菜；去的第一天直接给了我一台mac pro办公，上班期间可以去楼下健身房。都是免费！我们算法的小团建还去攀岩了，今年流利说的大团建还是去的日本，简直不能更赞了。</p><p><img src="/images/liulishuoenv.jpg" alt=""></p><h4 id="人">人</h4><p>再好的公司，再牛的技术，再人性化的制度也都是由人来完成的，流利说也真的让我看到一个有前途的创业公司是由什么人组成的。CEO Yi是Princeton PhD，杭州市理科高科状元（这事儿我在和ceo吃饭的时候就已经求证过了），CEO这人正能量满分，气场爆炸。CTO SJTU，在Quantcast从事数据分析，Ben口齿伶俐的一比，乐趣满分，四点准时健身房，肌肉满分。首席科学家Hui，也是我的boss，谷歌NLP工程师，UW PhD，完全的硅谷装束，大拖鞋，大polo衫，根本没什么特别的，但是却是最能将时下流行的算法运用到现实产品中去的实践者，nice到不行，只是一半时间在美国。</p><p>除了创始人之外，每个人都比我不知道高到哪里去了。我们有阿里的架构师和云后台架构，android端几乎是完全微信团队，后端都是得力的工匠，iOS都是资深手艺人，教研几乎都是语言学大牛，会好几门语言，产品设计运营ui了解不多。而算法团队，ouyang是带我的大哥，神，SJTU MS，人炒鸡好，写scala像写诗一样，看到就是满眼的阳光和活力。金神TJU MS，小极客一枚，认真细致到不行，吃过不少苦，教了我很多，事无巨细，没事还一起去同济游个泳。Chuan WUT MS，是一个智商及其高的人，一个人几乎自学了这个口上的所有知识，没事coursera上刷大牛的课，人炒鸡nice。还有feiteng，DUT MS，算法支柱，从hmm把玩到cnn，神。小飞中科院 MS，神。ruobin，NJU MS，神。当然还有很多别的神们。</p><p>这次实习更有幸接触了国外好学校的小伙子。hunter，室友，常青藤，统计大三，abc，也能称为神了，我经常恬不知耻的拿我的三脚猫口语去和他的三角猫中文碰撞，做过NASA的数据分析，浑身都是名校出身的光环，晃眼。tony，室友，MU／PSU ME，常青藤，在全球top的boston consalting Intern过，想法真的很新颖而且背后都带有商业的考量，聊了很多流利说的问题。ivision，室友，TJU CS的第一名，放弃了读研，追求前端新高度，把我秒成渣的本科生，已进阿里。xh，室友，NJU水文大四，运营小伙，也是初入互联网，但是玩的一手好游戏,老实的好小伙，难忘一起抽烟吹逼的日子。</p><p><img src="/images/lingoall2.jpg" alt=""></p><h4 id="我做的事儿">我做的事儿</h4><p>在这个满是神的世界，我自然也是要工作的。先总结一下我点亮的技能：scala函数式programme、python、zeppelin可视化工具、Spark、Bash。</p><p>前期第一个任务主要是折腾一个可视化环境能将大批量的打分数据可视化，看到每次调整映射后的分数分布。因为考虑到打分的数据量实在太大，传统浏览器是无法承载的，所以放弃了裸前端的做法，考虑到想要串起spark打分的自动化流程，所以用到了zeppelin这个开源平台，用集成好的D3做二次开发，因为类似于IDE online，所以可以轻松的写scala代码，但是搭不上我们自己的spark集群，这个事儿耽误了至少两周，那两周感觉做不出了，丢人到想悄悄背着包一走了之。后来我还是没有这么怂，发现了z-manager这个小插件，勉强搭上了自己的HDFS终于实现了第一个打分自动化图像，也最终搭建了流利说的zeppelin scorer可视化系统平台。</p><p>接下来做的第二件大事儿就是，<strong>写Scala</strong>，函数式编程真的很吸引人，代码量很少，只是很多时候理解起来有点吃力，主要写了将音频，文本序列化的一个脚本，因为数据量很大，所以需要搭spark跑整个task，发现自己写的实在太傻，在ouyang指导下，理解了executor和worker的关系，用了很多优秀模式，比如广播变量，封装了很多方法，使得代码运行效率从之前40多分钟提到10分钟，整个序列化任务可用于流利说之前所有英日韩德法的算法验证。中间还写了个从七牛上批量下载所有音频的小脚本。</p><p>第三件事儿，是将feiteng整个流利说打分验证对比可视化，从这里我才看到了流利说整个打分的严谨性，因为已经对整个搭建的环境很熟悉，所以这部分只需要做到整个流程上的逻辑连贯就可以了，最后在zeppelin上用angularjs将整个对比呈现，也是基本满意了。</p><p>期间还入门了python爬虫的基础，是为了批量抓取韩语发音网站上的音标，阅读了ouyang大量pipeline的代码，有很多工程上的写法，一直受用到我现在。每周的算法分享，也让我了解了大概的语音这块的形势。</p><p>值得欣慰的是，搭建的这个流利说的打分zeppelin平台，还被NFLabs fork到了zeppelinhub里作为visulization的tutorial。也算是对我这只小菜鸡的鼓励了吧，中间一个小系统可以<a href="https://www.zepl.com/viewer/notebooks/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2FsZXg0NGp6eS9ub3RlYm9vay16ZXBwZWxpbi9tYXN0ZXIvMkFWQlhaOVJXL25vdGUuanNvbg" target="_blank" rel="noopener">看下</a>。</p><p>可能我永远都写不完流利说的好，写不完备流利说的三个月实习让我开了多少眼，但是我知道，人还是要向前看，学到的工匠精神也好，大局观也好，编程的优秀模式也好，对待新技术的态度也好，还是需要一个真正的平台才能好好实战，感谢流利说和流利说的小伙伴给我的各种帮助，我也坚定了自己好好做技术的信念，就当一切都刚刚开始。</p><h3 id="关键词四：戒烟">关键词四：戒烟</h3><p>这块内容我其实也是最近才想到写的，一来是想让自己继续坚持下去，二来也是给想戒烟的人一点启示，或者是对待戒断综合症的一点见解。</p><p>首先是戒烟的动机，我烟龄没有很长，也就3年左右吧，但是瘾还是很大的。长远动机主要是明显觉得身体没有以前好了，有时候早上咳嗦很厉害，肺部也有时候不适，想恢复以前的好身体。近期的动机是觉得太花钱，而且很耽误时间，抽完烟就晕晕的想找个地儿躺着，感觉人的精神状态也变的慵懒了。很多人会觉得抽烟酷酷的，最近也在想这个事儿，我个人觉得是价值观的问题，从前的观念感觉抽根烟酷酷的，以至于好像不会抽烟有时是件有点丢脸的事儿，然而我们现在当下这个年代，做什么事儿是酷的呢，良好的健身习惯，良好的饮食习惯或许是完全的素食主义，晒今天消耗了多少卡路里，走了多少里程。这是当下越来越多的人在传递的东西，不管是你的周遭生活还是意识形态，因此是不是该是时候刷新一下自己的价值观了。</p><p><img src="/images/quit_smoking.jpg" alt=""></p><p>其实等你想清楚了这一切之后，戒烟就只是个坚持的事儿，其实每个人都一样，要想突破之前已经成型的习惯都会有阵痛，就像所有人都是懒的一样，所有人冬天也都不想起床，但是总有人可以打破这一切，那你为什么不可以。另外不要相信什么减量戒烟法，减量法只是给你想戒烟的一时冲动提供暂时的温床，等时间一长，这个想法不再一直激励你的时候也就没什么大用了。特别想抽烟的时候注意分散下注意力，有时候也可以给自己稍微强烈一点的心理暗示，比如再忍一忍之类的。我戒烟有个得天独厚的好处，就是周围没有人抽烟，也就没有人在那儿得得嗖嗖的诱惑你。</p><p>关于戒断后的一些不适应，我就非常强烈，比如胸闷，比抽烟时候更加胸闷，比如头晕，可以去适当看看中医。但是我觉得记住两句话就好了：</p><blockquote><p><strong>戒烟时的不适应都是暂时的，戒烟后的益处是永恒的。</strong></p></blockquote><p>我也才戒了两个月，最近已经基本回到以前那个不会抽烟的状态了，希望能保持吧。</p><h3 id="关于2016的Todo">关于2016的Todo</h3><p>今年有几件大事儿，第一就是毕业，我六月份要毕业答辩，所以这是一定会认真对待的。另外就是去工作，这也是我今年最为未知的事儿。主要的Todo List还是会列一下：</p><ul><li>规律作息和生活，这个也说了很久了，是时候来实践一下了。</li><li>减肥健身管住嘴，控制体重150以内，现在超标太多。</li><li>写一篇高质量的SCI，然后毕业答辩。</li><li>mooc坚持看，不管是慕课网还是coursera，坚持下去。</li><li>前端的基础打牢，学node，学点新框架reactjs，vuejs，redux，koa。</li><li>今年计划好好学一门英语以外的外语，暂定韩语或者日语吧。</li></ul><blockquote><p><strong>有時，換個思考方式，事情就容易做很多。例如健身，一次一小時，一週四次，一個月16小時，一年才做總共8天。為什麼，不好好的把握每一次做的機會？學習、工作⋯亦然。 – 蒋劲夫</strong></p></blockquote><h3 id="2016，共勉！">2016，共勉！</h3>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/2016pic.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;总体&quot;&gt;总体&lt;/h3&gt;
&lt;p&gt;2015还是随风而逝了，有点感伤，也有点感动。感伤于很多时候的无所事事，耽误了不少时间。感动在于一整年也算是完成了自己的一系列小目标，看到了自己的成长。而更多的是看到了_&lt;strong&gt;差距&lt;/strong&gt;_，差距来源于自己视野的不断打开，正因为如此我会有很多对自己的不满足，不满足于自己的拖沓，不满足于自己的懒散。但总体来说这些都算是人生路上的好事，因为我不可能永远停留着做个学生，这一年我也看到了自己的多面性，因为人这一生也是自我认识的过程。&lt;br&gt;
&lt;strong&gt;2015年足迹经过了无锡，上海，杭州，重庆，成都，哈尔滨。&lt;/strong&gt;&lt;br&gt;
过去的旧时光我都记得，但是更重要的还是向前看。我还是要对自己的2015做一些总结。&lt;br&gt;
2015对我而言的年度词语我觉得应该是——&lt;strong&gt;开眼&lt;/strong&gt;。&lt;/p&gt;
    
    </summary>
    
      <category term="生活感悟" scheme="http://alexjiangzy.com/categories/%E7%94%9F%E6%B4%BB%E6%84%9F%E6%82%9F/"/>
    
    
      <category term="年终总结" scheme="http://alexjiangzy.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Mactex+Sublime3+Skim集成环境安装及LaTeX常用命令</title>
    <link href="http://alexjiangzy.com/2015/12/16/MactexTool/"/>
    <id>http://alexjiangzy.com/2015/12/16/MactexTool/</id>
    <published>2015-12-16T13:38:48.000Z</published>
    <updated>2018-03-19T14:48:46.000Z</updated>
    
    <content type="html"><![CDATA[<!-- ![](http://www.macupdate.com/images/icons256/12104.png) --><p>刚转战到mac上来工作，涉及到文本的书写离开了X软的Word还是有些许不适应，恰逢最近写paper也决心把latex好好折腾下。遂搜了一大顿帖子，发现了**<code>MacTex+Sublime3+Skim</code>**的爆炸组合，做点小总结。所以本文主要涉及以下几个核心方面：</p><a id="more"></a><ul><li>MacTex+Sublime3+Skim安装</li><li>MacTex中文支持</li><li>LaTeX的一些特殊字符总结</li></ul><hr><h3 id="MacTex的安装">MacTex的安装</h3><p><a href="https://tug.org/mactex/" target="_blank" rel="noopener">Mactex官网</a>进行安装即可，无脑操作，一步步往下点。值得注意的是可以只下载精简版就好，虽然我还是装了那个2G多的大胖子。</p><h3 id="Sublime3-LaTeXTools-的安装">Sublime3(LaTeXTools)的安装</h3><p><a href="www.sublimetext.com">Sublime3</a>官网进行安装，这里就不赘述了，它强大的工具插件集成，轻量级的架构都是很吸引人的地方，对比于之前把玩的<a href="atom.org">Atom</a>，常常卡掉也懒得弄了。这里需要做的是既然选择了Sublime作为编辑器，就需要下载一个Sublime中针对LaTeX的给力插件——<a href="https://github.com/SublimeText/LaTeXTools" target="_blank" rel="noopener"><strong>LaTeXTools</strong></a>，支持直接编译和代码高亮。<br>打开Sublime，<code>command</code>+<code>shift</code>+<code>P</code>，输入<code>pi</code>(Package Control: Install Package)，进入了插件安装页面输入<br><code>LaTeXTools</code>，确认后安装就OK了。</p><h3 id="Skim的安装">Skim的安装</h3><p>Skim是一款PDF查看软件，同样是在<a href="http://skim-app.sourceforge.net/" target="_blank" rel="noopener">Skim官方网站</a>上下载后进行无脑安装，用于tex文件编译完成后查看，同步配置是为了能和Sublime编辑器同步，在Sublime中编译后(command+B)，直接可以在Skim中同步看到效果。<br><strong><code>打开skim&gt;点击Skim选项&gt;选择同步&gt;改写一些配置</code></strong></p><table><thead><tr><th>预设</th><th>自定义</th></tr></thead><tbody><tr><td>命令</td><td>/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl</td></tr><tr><td>参数</td><td>“%file”:%line</td></tr></tbody></table><p><img src="/images/skimsync.png" alt=""></p><h3 id="MacTex的中文支持">MacTex的中文支持</h3><p>中文支持是我很恼火的一个问题，因为早两年我在windows上用LaTeX的时候就发觉中文的支持贼差，但是现在国内一些较好的杂志(如_《中国科学》<em>、</em>《物理学报》_)又纷纷推出了LaTeX的模版，好比硬要给拖拉机装上12缸😊，在mac上折腾发现也没有那么轻松，在此我也说句良心话，用LaTeX还是尽量去敲英文吧。参考了一些人的博客，大致有三种方式可以实现。</p><ul><li>最简便的方法，只需要在tex文件的最上部加上<code>%!TEX program = xelatex</code>就可以了，参考下文的代码和最终结果，但是这里需要多注意两个问题：<ul><li>一定要注意设定字体。参考下文中的 <code>\setmainfont{Hiragino Sans GB}</code>。</li><li>中文一定要多加一句代码<code>\XeTeXlinebreaklocale &quot;zh&quot;</code>，这是为了中文可以正常换行。</li></ul></li></ul><pre><code class="language-ruby">%!TEX program = xelatex%!TEX TS-program = xelatex%!TEX encoding = UTF-8 Unicode\documentclass[12pt]{article} %这个我就不多说了，头文件\usepackage{url} %这个我也不多说了\usepackage{fontspec,xltxtra,xunicode} %最新的mactex都有\defaultfontfeatures{Mapping=tex-text}\setromanfont{Heiti SC} %设置中文字体\XeTeXlinebreaklocale “zh”\XeTeXlinebreakskip = 0pt plus 1pt minus 0.1pt %文章内中文自动换行，可以自行调节\newfontfamily{\H}{Songti SC} %设定新的字体快捷命令\newfontfamily{\E}{Weibei SC} %设定新的字体快捷命令\begin{document}\thispagestyle{empty}\small{给一个比较简单的方法，在mac上折腾CJK有点麻烦，其实XeTeX就可以解决中文的问题。编码的改动其实不需要在mactex的设置里面改，写在前面然后注释掉就好了。\\繁體字什麼的也是可以實現的。\\当你需要打不同字体的时候，就需要用到这个\url{\newfontfamily}，这样你可以在一行中显示多种字体。比如说：\\}\Huge{{\H 宋体} {\E 魏碑} 黑体}\end{document}</code></pre><p><img src="/images/latexPic.png" alt=""></p><ul><li>比较折腾的方法，就是去折腾这个<a href="http://cjk.ffii.org/" target="_blank" rel="noopener">CJK</a>。其实也没有很折腾，我下载的最新的mactex2015版，CJK已经集成在其中了，不用单独下载了，只要在文档开头加上<code>\usepackage[encapsulated]{CJK} </code>就可以了。具体参看下面的测试tex：</li></ul><pre><code class="language-ruby">\documentclass[12pt]{article}\usepackage[encapsulated]{CJK} % 1. Dec. 2009更新：使用[encapsulate]才是正確的用法\begin{document}\begin{CJK}{UTF8}{bsmi} % 開始 CJK 環境，設定編碼，設定字體“互联网能造空调吗？互联网永远都只是工具，不能替代实体经济。”在互联网是万能的甚至可以颠覆、通杀一切的说法在中国商界流行之时，董明珠仍然坚持自己观点。她给在场企业家鼓劲说，不信再等两年来看，实体经济一定会崛起。无论什么企业和公司，都要有自己的核心技术。实体店也会一直存在下去，但在互联网时代发生很大变化。实体店应该在便利性等方面下功夫，让顾客感觉跟自己更近。This is a test.\end{CJK} % 結束 CJK 環境\end{document}</code></pre><ul><li>比较严谨的方法，安装latexmk，然后在LaTeXTools中更改设置，首先</li></ul><pre><code class="language-bash">sudo tlmgr update --selfsudo tlmgr install latexmk</code></pre><p>然后在Sulime中打开<code>Preferences——Package Settings——LaTeXTools——Settings-User</code>，在builder－settings中进行如下设置（<strong>记住一定是在builder-settings这个选项下进行设置</strong>）：</p><pre><code class="language-json">&quot;program&quot; : &quot;xelatex&quot;,&quot;command&quot; : [&quot;latexmk&quot;, &quot;-cd&quot;, &quot;-e&quot;, &quot;$pdflatex = 'xelatex -interaction=nonstopmode -synctex=1 %S %O'&quot;, &quot;-f&quot;, &quot;-pdf&quot;],</code></pre><p>更加具体的配置和一些问题，可以访问<a href="http://www.readern.com/sublime-text-latex-chinese-under-mac.html" target="_blank" rel="noopener">Readern博客</a>，特别是如果编译出现问题可以多follow下面的评论。</p><h3 id="LaTeX的命令总结">LaTeX的命令总结</h3><p>这部分我会在具体一边做的时候一边补充，所以我先慢慢整理。</p><h4 id="LaTeX对应的所有希腊字母：">LaTeX对应的所有希腊字母：</h4><p>这部分顺便测试这个Hexo里的MathJax。</p><table><thead><tr><th>\alpha</th><th style="text-align:center">$\alpha$</th><th>\beta</th><th style="text-align:center">$\beta$</th><th>\gamma</th><th style="text-align:center">$\gamma$</th></tr></thead><tbody><tr><td>\delta</td><td style="text-align:center">$\delta$</td><td>\epsilon</td><td style="text-align:center">$\epsilon$</td><td>\zeta</td><td style="text-align:center">$\zeta$</td></tr><tr><td>\eta</td><td style="text-align:center">$\eta$</td><td>\theta</td><td style="text-align:center">$\theta$</td><td>\iota</td><td style="text-align:center">$\iota$</td></tr><tr><td>\kappa</td><td style="text-align:center">$\kappa$</td><td>\lambda</td><td style="text-align:center">$\lambda$</td><td>\mu</td><td style="text-align:center">$\mu$</td></tr><tr><td>\nu</td><td style="text-align:center">$\nu$</td><td>\xi</td><td style="text-align:center">$\xi$</td><td>\omicron</td><td style="text-align:center">$\omicron$</td></tr><tr><td>\pi</td><td style="text-align:center">$\pi$</td><td>\rho</td><td style="text-align:center">$\rho$</td><td>\sigma</td><td style="text-align:center">$\sigma$</td></tr><tr><td>\tau</td><td style="text-align:center">$\tau$</td><td>\upsilon</td><td style="text-align:center">$\upsilon$</td><td>\phi</td><td style="text-align:center">$\phi$</td></tr><tr><td>\chi</td><td style="text-align:center">$\chi$</td><td>\psi</td><td style="text-align:center">$\psi$</td><td>\omega</td><td style="text-align:center">$\omega$</td></tr></tbody></table><h4 id="公式：">公式：</h4><p>单行公式:</p><pre><code>$$a = b + c$$</code></pre><p>$$a = b + c$$<br>稍复杂的公式：</p><pre><code>$$\frac{\partial u}{\partial t}= h^2 \left( \frac{\partial^2 u}{\partial x^2} +\frac{\partial^2 u}{\partial y^2} +\frac{\partial^2 u}{\partial z^2}\right)$$</code></pre><p>$$\frac{\partial u}{\partial t}<br>= h^2 \left( \frac{\partial^2 u}{\partial x^2} +<br>\frac{\partial^2 u}{\partial y^2} +<br>\frac{\partial^2 u}{\partial z^2}\right)$$<br>薛定谔方程：</p><pre><code>$$ i\hbar\frac{\partial \psi}{\partial t}= \frac{-\hbar^2}{2m} \left(\frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} + \frac{\partial^2}{\partial z^2}\right) \psi + V \psi.$$</code></pre><p>$$ i\hbar\frac{\partial \psi}{\partial t}<br>= \frac{-\hbar^2}{2m} \left(<br>\frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} + \frac{\partial^2}{\partial z^2}<br>\right) \psi + V \psi.$$</p>]]></content>
    
    <summary type="html">
    
      &lt;!-- ![](http://www.macupdate.com/images/icons256/12104.png) --&gt;
&lt;p&gt;刚转战到mac上来工作，涉及到文本的书写离开了X软的Word还是有些许不适应，恰逢最近写paper也决心把latex好好折腾下。遂搜了一大顿帖子，发现了**&lt;code&gt;MacTex+Sublime3+Skim&lt;/code&gt;**的爆炸组合，做点小总结。所以本文主要涉及以下几个核心方面：&lt;/p&gt;
    
    </summary>
    
      <category term="工具Tools" scheme="http://alexjiangzy.com/categories/%E5%B7%A5%E5%85%B7Tools/"/>
    
    
      <category term="mactex" scheme="http://alexjiangzy.com/tags/mactex/"/>
    
  </entry>
  
  <entry>
    <title>atom的强力plugin:activate-power-mode</title>
    <link href="http://alexjiangzy.com/2015/12/14/atomPlugin/"/>
    <id>http://alexjiangzy.com/2015/12/14/atomPlugin/</id>
    <published>2015-12-14T15:02:56.000Z</published>
    <updated>2015-12-17T02:59:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近朋友圈狂晒Atom activate-power-mode强力驱动的编辑器插件。本人体验了下之后果然是有点酸爽，故写个小教程来小纪录一下。</p><h2 id="工具">工具</h2><ul><li>mac osx</li><li><a href="atom.io">atom</a>, 值得一提的是atom是github主推的下一代编辑器，未来还是值得期待。</li></ul><a id="more"></a><h2 id="安装atom">安装atom</h2><pre><code class="language-bash">  $ brew install Caskroom/cask/atom #在安装homebrew的前提下</code></pre><h2 id="安装plugin-activate-power-mode">安装plugin:activate-power-mode</h2><pre><code class="language-bash">  $ cd ~/.atom/packages  $ git clone https://github.com/JoelBesada/activate-power-mode.git  $ cd activate-power-mode  $ apm install</code></pre><h2 id="打开atom编辑器右键点击激活">打开atom编辑器右键点击激活</h2><p>进入atom，随便打开一个页面，右键点击Toggle activate-power-mode<br><img src="/images/pluginToggle.jpg" alt=""></p><h2 id="效果">效果</h2><p>在最终效果之前，可以先点击下面的播放键，是不是感觉像在开高达，双脚抖起来打小怪兽喽～</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width="330" height="86" src="http://music.163.com/outchain/player?type=2&id=26625301&auto=0&height=66"></iframe>![](http://www.dvel.cc/upload/2015/12/b8f249ec-9605-11e5-978c-eb3bb21eecd8.gif)]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近朋友圈狂晒Atom activate-power-mode强力驱动的编辑器插件。本人体验了下之后果然是有点酸爽，故写个小教程来小纪录一下。&lt;/p&gt;
&lt;h2 id=&quot;工具&quot;&gt;工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;mac osx&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;atom.io&quot;&gt;atom&lt;/a&gt;, 值得一提的是atom是github主推的下一代编辑器，未来还是值得期待。&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="工具Tools" scheme="http://alexjiangzy.com/categories/%E5%B7%A5%E5%85%B7Tools/"/>
    
    
      <category term="前端" scheme="http://alexjiangzy.com/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
</feed>
