
LeetCode
文章平均质量分 79
Katle
这个作者很懒,什么都没留下…
展开
-
leetcode二叉搜索树中的插入操作
二叉搜索树中的插入操作给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。例如,给定二叉搜索树: 4 / \ 2 7 / \1 3和 插入的值: 5你可以返回这个二叉搜索树: 4 / \ 2 7 / \ /1 3 5或者这个树也是有效的原创 2020-09-16 16:50:48 · 156 阅读 · 0 评论 -
leetcode最长回文子串(js实现)
最长回文子串给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。示例 1:输入: “babad”输出: “bab”注意: “aba” 也是一个有效答案。示例 2:输入: “cbbd”输出: “bb”解题思路中心扩展法1.回文串一定是对称的每次选择一个中心,进行中心向两边扩展比较左右字符是否相等。2.中心点的选取有两种:aba:中心点是baa:中心点是两个a之间3.所以共有两种组合可能left:i,right:i原创 2020-09-16 16:43:10 · 1081 阅读 · 1 评论 -
leetcode 对角线遍历(js实现)
对角线遍历给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。示例:输入: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]输出: [1,2,4,7,5,3,6,8,9]解释:解题思路两种移动方向:右上移动:i -= 1,j += 1左下移动:i+=1,j-=1处理上下左右编辑问题越界即转弯/** * @param {number[][]} matr原创 2020-09-16 10:08:19 · 541 阅读 · 0 评论 -
leetcode 旋转矩阵(js实现)
旋转矩阵给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。示例 1:给定 matrix = [ [1,2,3], [4,5,6], [7,8,9] ],原地旋转输入矩阵,使其变为: [ [7,4,1], [8,5,2], [9,6,3] ]示例 2:给定 matrix = [[ 5, 1, 9,11],[ 2, 4, 8,10],[13, 3, 6, 7],[15,14,12,16]],原创 2020-09-15 16:21:21 · 1341 阅读 · 0 评论 -
leetcode 索引插入位置(js实现)
搜索插入位置给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。示例 1:输入: [1,3,5,6], 5输出: 2示例 2:输入: [1,3,5,6], 2输出: 1示例 3:输入: [1,3,5,6], 7输出: 4示例 4:输入: [1,3,5,6], 0输出: 0解题思路暴力循环遍历将目标值先直接插入数组中,将数组按递增序列排序,循环遍历数组第一次,去原创 2020-09-15 10:39:23 · 165 阅读 · 0 评论 -
leetcode寻找数组的中心索引(js实现)
寻找数组的中心索引给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。示例 1:输入:nums = [1, 7, 3, 6, 5, 6]输出:3解释:索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6原创 2020-09-15 10:19:37 · 245 阅读 · 0 评论 -
keetcode 将有序数组转化为二叉搜索树(js实现)
将有序数组转换为二叉搜索树将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。示例:给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:解题思路递归+二叉搜索树的特点1.中序遍历二叉搜索树,得到一个递增序列,头节点将递增序列分为左右两部分;2.二叉搜索树的左子树都小于头节点,为递增序列的前一部分;3原创 2020-09-02 16:27:02 · 244 阅读 · 0 评论 -
leetcode 平衡二叉树(js实现)
平衡二叉树给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。示例 1:给定二叉树 [3,9,20,null,null,15,7]返回 true 。示例 2:给定二叉树 [1,2,2,3,3,null,null,4,4]返回 false 。解题思路自定上下求二叉树的高度差1.递归求解左右子树的高度,判断左右子树的高度差是否大于1:若大于1,则表示不是高度平衡二叉树,返回false;原创 2020-09-02 15:52:18 · 220 阅读 · 0 评论 -
leetcode 存在重复元素|||(js实现)
存在重复元素 III在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。如果存在则返回 true,不存在返回 false。示例 1:输入: nums = [1,2,3,1], k = 3, t = 0输出: true示例 2:输入: nums = [1,0,1,1], k = 1, t = 2输出: true示例 3:输入: nums = [1,5,9,1,5,原创 2020-09-02 11:59:09 · 303 阅读 · 0 评论 -
leetcode二叉搜索树的最近公共祖先(js实现)
二叉搜索树的最近公共祖先给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]示例 1:输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8输出:原创 2020-09-02 11:23:35 · 393 阅读 · 0 评论 -
leetcode数据流中第k大元素(js实现)
数据流中的第K大元素设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。示例:int k = 3;int[] arr = [4,5,8,2];KthLargest kthLargest = new KthLargest(3, arr);kthLargest.add(3)原创 2020-09-02 09:59:51 · 424 阅读 · 0 评论 -
leetcode删除二叉搜索树中的节点(js实现)
删除二叉搜索树中的节点给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。一般来说,删除节点可分为两个步骤:首先找到需要删除的节点;如果找到了,删除它。说明: 要求算法时间复杂度为 O(h),h 为树的高度。示例:root = [5,3,6,2,4,null,7]key = 35/ 3 6/ \ 2 4 7给定需要删除的节点值是 3,所以我们首先原创 2020-09-01 11:56:49 · 177 阅读 · 0 评论 -
leetcode二叉搜索树中实现插入操作(js实现)
二叉搜索树中的插入操作给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。例如,给定二叉搜索树: 4 / \ 2 7 / \1 3和 插入的值: 5你可以返回这个二叉搜索树: 4 / \ 2 7 / \ /1 3 5或者这个树也是有效的原创 2020-09-01 10:15:39 · 205 阅读 · 0 评论 -
leetcode二叉搜索树中的搜索(js实现)
二叉搜索树中的搜索给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。例如,给定二叉搜索树: 4 / \ 2 7 / \1 3和值: 2你应该返回如下子树: 2 / \ 1 3在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。解题思路递归求解根据二叉搜索树的特性:1.如果目标值等于节点的值,则返回节点原创 2020-09-01 09:24:03 · 150 阅读 · 0 评论 -
leetcode二叉搜索树迭代器(js实现)
二叉搜索树迭代器实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。调用 next() 将返回二叉搜索树中的下一个最小的数示例:BSTIterator iterator = new BSTIterator(root);iterator.next(); //返回 3iterator.next(); // 返回 7iterator.hasNext(); // 返回 trueiterator.next(); // 返回 9iterator.hasNext(原创 2020-08-31 18:58:00 · 191 阅读 · 0 评论 -
leetcode二叉树的序列化与反序列化(js实现)
二叉树的序列化与反序列化序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 /反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。示例:你可以将以下二叉树: 1 / \ 2 3 / \ 4 5序列化为 “[原创 2020-08-28 10:55:42 · 323 阅读 · 0 评论 -
leetcode 填充每一个节点的下一个右侧节点指针(js是实现)
填充每个节点的下一个右侧节点指针给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:struct Node {int val;Node *left;Node *right;Node *next;}填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。初始状态下,所有 next 指针都被设置为 NULL。示例:解题思路仔细观察题目的意思,就可以知道,这就是层序遍历的问题:可原创 2020-08-27 11:26:19 · 260 阅读 · 0 评论 -
leetcode从前序与中序遍历序列构造二叉树(js实现)
从前序与中序遍历序列构造二叉树根据一棵树的前序遍历与中序遍历构造二叉树。注意: 你可以假设树中没有重复的元素。例如,给出前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树: 3 / \ 9 20 / \ 15 7解题思路递归,自顶向下求解根据前序遍历和中序遍历的特点,前序遍历中,序列的第一个元素就是头节点的位置,获取头节点,在中序遍历中可根据头节点的值,返回头节点在中序遍原创 2020-08-27 10:18:43 · 779 阅读 · 0 评论 -
leetcode 从中序遍历和后序遍历构造二叉树(js实现)
从中序与后序遍历序列构造二叉树根据一棵树的中序遍历与后序遍历构造二叉树。注意: 你可以假设树中没有重复的元素。例如,给出中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7,20,3]返回如下的二叉树: 3 / \ 9 20 / \ 15 7解题思路递归,从上到下求解,根据中序遍历和后序遍历的特点,可求得:1.头节点位于后序遍历的最后一个元素;2.头节点可将中序遍历分为左右两部分;3.中序遍原创 2020-08-27 09:49:58 · 409 阅读 · 0 评论 -
leetcode 路径总和(js实现)
路径总和给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。说明: 叶子节点是指没有子节点的节点。示例: 给定如下二叉树,以及目标和 sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5-&原创 2020-08-26 10:38:53 · 366 阅读 · 0 评论 -
leetcode 二叉树的后序遍历(js实现)
二叉树的后序遍历给定一个二叉树,返回它的 后序 遍历。示例: 解题思路后序遍历:遍历顺序为:左子树->右子树->头节点/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } *//** * @param {TreeNode} root * @return {nu原创 2020-08-25 16:08:10 · 316 阅读 · 0 评论 -
leetcode 二叉树的前序遍历(js实现)
二叉树的前序遍历给定一个二叉树,返回它的 前序 遍历。示例:解题思路前序遍历:头节点->左子树->右子树/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } *//** * @param {TreeNode} root * @return {number原创 2020-08-25 15:40:52 · 427 阅读 · 0 评论 -
leetcode 二叉树的中序遍历(js实现)
二叉树的中序遍历给定一个二叉树,返回它的中序 遍历。示例:输入: [1,null,2,3]输出: [1,3,2]解题思路中序遍历:左孩子,中节点,右孩子使用递归实现,借助一个辅助函数,递归调用,一次递归左孩子,中节点和右孩子/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null;原创 2020-08-24 17:24:24 · 548 阅读 · 0 评论 -
leetcode 奇偶链表(js实现)
奇偶链表给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。示例 1:输入: 1->2->3->4->5->NULL输出: 1->3->5->2->4->NULL示例 2:输入: 2->1->3->5->原创 2020-08-24 11:11:08 · 294 阅读 · 0 评论 -
leetcode 无重复字符的最长字串(js实现)
无重复字符的最长子串给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: “abcabcbb”输出: 3解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。示例 2:输入: “bbbbb”输出: 1解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。示例 3:输入: “pwwkew”输出: 3解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke"原创 2020-08-21 10:23:17 · 345 阅读 · 0 评论 -
leetcode 字母异位词分组(js实现)
字母异位词分组给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。示例:输入: [“eat”, “tea”, “tan”, “ate”, “nat”,“bat”]输出:[ [“ate”,“eat”,“tea”], [“nat”,“tan”], [“bat”]]解题思路暴力求解分别遍历数组和数组的每一个元素,将每一个元素按字符串数组表示后进行升序排序,在利用join()内置函数将字符串数组转化为字符串,判断元素内是否有相同的字符串,有,则将原来的原创 2020-08-21 08:53:33 · 919 阅读 · 0 评论 -
leetcode 矩阵置零(js实现)
矩阵置零给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。示例 1:输入: [ [1,1,1], [1,0,1], [1,1,1] ]输出: [ [1,0,1], [0,0,0], [1,0,1] ]示例 2:输入: [ [0,1,2,0], [3,4,5,2], [1,3,1,5] ]输出: [ [0,0,0,0], [0,4,5,0], [0,3,1,0] ]解题思路利原创 2020-08-20 16:35:39 · 418 阅读 · 0 评论 -
leetcode三数之和(js实现)
三数之和给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。示例:给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]解题思路最开始的思路是暴力三层循环嵌套解决,但是提示超时,后来通过查看官网题解进行优化成两层循环。首先将数组进行升序排序原创 2020-08-20 15:30:08 · 776 阅读 · 0 评论 -
leetcode缺失数字(js实现)
缺失数字给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。示例 1:输入: [3,0,1]输出: 2示例 2:输入: [9,6,4,2,3,5,7,0,1]输出: 8解题思路暴力解决通过观察可知,缺失的数字就是数组长度len的序列和数组元素的比较,基于这一点,首先将数组按升序序列排序,再同时遍历数组长度和数组元素,逐一比较两个值是否相同,若是相同则继续遍历,不同则返回i,结束循环。/** * @param {num原创 2020-08-20 10:46:22 · 326 阅读 · 0 评论 -
leetcode有效的括号(js实现)
有效的括号给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。示例 1:输入: “()”输出: true示例 2:输入: “()[]{}”输出: true示例 3:输入: “(]”输出: false示例 4:输入: “([)]”输出: false示例 5:输入: “{[]}”输出: true原创 2020-08-20 10:09:27 · 766 阅读 · 0 评论 -
leetcode 杨辉三角(js实现)
杨辉三角给定一个非负整数 numRows,生成杨辉三角的前 numRows行。在杨辉三角中,每个数是它左上方和右上方的数的和。示例:输入: 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]解题思路嵌套循环遍历,将杨辉三角的每一层作为外部遍历,每一层中的元素,作为内部遍历,第1层1个元素,第二层2个元素,依次将元素推入数组row中,其次,每一层元素的第0个和最后一个元素值都为1,中间的元素值为res[i-1][j-1]+res[i原创 2020-08-19 15:44:43 · 371 阅读 · 0 评论 -
leetcode 颠倒二进制位(js实现)
颠倒二进制位颠倒给定的 32 位无符号整数的二进制位。示例 1:输入: 00000010100101000001111010011100输出:00111001011110000010100101000000解释: 输入的二进制串00000010100101000001111010011100 表示无符号整数 43261596,因此返回 964176192,其二进制表示形式为00111001011110000010100101000000。示例 2:输入:111111111111111原创 2020-08-19 11:11:07 · 332 阅读 · 0 评论 -
leetcode汉明距离(js实现)
汉明距离两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。给出两个整数 x 和 y,计算它们之间的汉明距离。注意: 0 ≤ x, y < 231.示例:输入: x = 1, y = 4输出: 2解释:1 (0 0 0 1)4 (0 1 0 0)上面的箭头指出了对应二进制位不同的位置。解题思路布赖恩.克尼根算法布赖恩.克尼根算法用于快速判断二进制中有多少个1,借助num&(num-1)来直接去除num的二进制中最右边的1。但是在题目中给原创 2020-08-19 10:03:30 · 287 阅读 · 0 评论 -
leetcode 位1的个数(js实现)
位1的个数编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。示例 1:输入:00000000000000000000000000001011输出:3解释:输入的二进制串00000000000000000000000000001011 中,共有三位为 ‘1’。示例 2:输入:00000000000000000000000010000000输出:1解释:输入的二进制串00000000000000000000000010000000 中原创 2020-08-19 09:29:32 · 357 阅读 · 0 评论 -
leetcode罗马数字转整数(js实现)
罗马数字转整数罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。字符 数值I 1V 5X 10L 50C 100D 500M 1000例如, 罗马数字 2写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。通原创 2020-08-18 11:06:05 · 384 阅读 · 0 评论 -
leetcode 3的幂(js实现)
3的幂给定一个整数,写一个函数来判断它是否是 3 的幂次方。示例 1:输入: 27输出: true示例 2:输入: 0输出: false示例 3:输入: 9输出: true示例 4:输入: 45输出: false解题思路循环迭代找出数字n是否时3的幂,只要求n%3的余数为0,就一直将n/3执行,因此,可以将n除以3执行x次,最终的结果为1的话,则返回true,若是n<1,则返回false。/** * @param {number} n * @r原创 2020-08-18 08:39:06 · 432 阅读 · 0 评论 -
leetcode 计数质数(js实现)
计数质数统计所有小于非负整数 n 的质数的数量。示例:输入: 10 输出: 4 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。解题思路厄拉多塞筛法从2开始,每遍历到一个数,将它的倍数去掉,直到遍历到n,再计算剩下多少个数var countPrimes = function(n) { var num = 0 var signs = [] for(var i=2;i<n;i++){ if(!signs[i]){原创 2020-08-18 07:58:46 · 231 阅读 · 0 评论 -
leetcode Fizz Buzz(js实现)
Fizz Buzz写一个程序,输出从 1 到 n 数字的字符串表示。如果 n 是3的倍数,输出“Fizz”;如果 n 是5的倍数,输出“Buzz”;3.如果 n 同时是3和5的倍数,输出 “FizzBuzz”。示例:n = 15,返回: [“1”,“2”,“Fizz”,“4”,“Buzz”,“Fizz”,“7”,“8”,“Fizz”,“Buzz”,“11”,“Fizz”,“13”,“14”,“FizzBuzz” ]解题思路暴力解决根据题目原创 2020-08-17 14:19:47 · 694 阅读 · 0 评论 -
leetcode第一个错误的版本(js实现)
第一个错误的版本你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。示例:给定 n原创 2020-08-13 14:55:09 · 406 阅读 · 0 评论 -
leetcode合并两个有序数组(js实现)
合并两个有序数组给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。说明:初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。示例:输入:nums1 = [1,2,3,0,0,0], m = 3nums2 = [2,5,6], n = 3输出: [1,2,2,3,5,6]解题思路原创 2020-08-13 14:14:51 · 847 阅读 · 0 评论