C/C++调用Python方法与技巧

一、 单项选择题

一、初始化

在调用Python模块时需要首先包含Python.h头文件,这个头文件一般在安装的Python目录中的 include文件中,所在VS中首先需要将这个路径加入到项目中

包含完成之后可能会抱一个错误:找不到 inttypes.h文件,在个错误在Windows平台上很常见,如果报这个错误,需要去网上下载对应的inttypes.h文件然后放入到对应的目录中即可,我这放到VC的include目录中

在包含这些文件完成之后可能还会抱一个错误,未找到Python36_d.lib 在Python环境中确实找不到这个文件,这个时候可以修改pyconfig.h文件,将这个lib改为python36.lib。

还有一点要注意,下载的Python环境必须的与目标程序的类型相同,比如你在VS 中新建一个Win32项目,在引用Python环境的时候就需要引用32位版本的Python

这些准备工作做完后在调用Python前先调用Py_Initialize 函数来初始化Python环境,之后我们可以调用Py_IsInitialized来检测Python环境是否初始化成功
下面是一个初始化Python环境的例子

BOOL Init()
{
	Py_Initialize();

	return Py_IsInitialized();
}

二、调用Python模块

调用Python模块可以简单的调用Python语句也可以调用Python模块中的函数。

简单调用Python语句

针对简单的Python语句(就好像我们在Python的交互式环境中输入的一条语句那样),可以直接调用 PyRun_SimpleString 函数来执行, 这个函数需要一个Python语句的ANSI字符串作为参数,返回int型的值。如果为0表示执行成功否则为失败

void ChangePyWorkPath(LPCTSTR lpWorkPath)
{
	TCHAR szWorkPath[MAX_PATH + 64] = _T("");
	StringCchCopy(szWorkPath, MAX_PATH + 64, _T("sys.path.append(\""));
	StringCchCat(szWorkPath, MAX_PATH + 64, lpWorkPath);
	StringCchCat(szWorkPath, MAX_PATH + 64, _T("\")"));

	PyRun_SimpleString("import sys");
	USES_CONVERSION;

	int nRet = PyRun_SimpleString(T2A(szWorkPath));
	if (nRet != 0)
	{
		return;
	}
}

这个函数主要用来将传入的路径加入到当前Python的执行环境中,以便可以很方便的导入我们的自定义模块
函数首先通过字符串拼接的方式组织了一个 "sys.path.append('path')" 这样的字符串,其中path是我们传进来的参数,然后调用PyRun_SimpleString执行Python的"import sys"语句来导入sys模块,接着执行之前拼接的语句,将对应路径加入到Python环境中

三、调用Python模块中的函数

调用Python模块中的函数需要执行之前说的2~7的步骤

  1. 加载Python模块(自定义模块)

加载Python的模块需要调用 PyImport_ImportModule 这个函数需要传入一个模块的名称作为参数,注意:这里需要传入的是模块的名称也就是py文件的名称,不能带.py后缀。

这个函数会返回一个Python对象的指针,在C++中表示为PyObject。这里返回模块的对象指针

  1. 然后调用 PyObject_GetAttrString 函数来加载对应的Python模块中的方法,这个函数需要两个参数,第一个是之前获取到的对应模块的指针,第二个参数是函数名称的ANSI字符串。这个函数会返回一个对应Python函数的对象指针。后面需要利用这个指针来调用Python函数

获取到函数的指针之后我们可以调用 PyCallable_Check 来检测一下对应的对象是否可以被调用,如果能被调用这个函数会返回true否则返回false

  1. 接着就是传入参数了,Python中函数的参数以元组的方式传入的,所以这里需要先将要传入的参数转化为元组,然后调用 PyObject_CallObject 函数来执行对应的Python函数。这个函数需要两个参数第一个是上面Python函数对象的指针,第二个参数是需要传入Python函数中的参数组成的元组。函数会返回Python的元组对象,这个元组就是Python函数的返回值
  2. 获取到返回值之后就是解析参数了,我们可以使用对应的函数将Python元组转化为C++中的变量
  3. 最后需要调用 Py_DECREF 来解除Python对象的引用,以便Python的垃圾回收器能正常的回收这些对象的内存

下面是一个传入空参数的例子

void GetModuleInformation(IN LPCTSTR lpPyFileName, OUT LPTSTR lpVulName, OUT long& level)
{
	USES_CONVERSION;

	PyObject *pModule = PyImport_ImportModule(T2A(lpPyFileName)); //加载模块
	if (NULL == pModule)
	{
		g_OutputString(_T("加载模块[%s]失败"), lpPyFileName);
		goto __CLEAN_UP;
	}

	PyObject *pGetInformationFunc = PyObject_GetAttrString(pModule, "getInformation"); // 加载模块中的函数
	if (NULL == pGetInformationFunc || !PyCallable_Check(pGetInformationFunc))
	{
		g_OutputString(_T("加载函数[%s]失败"), _T("getInformation"));
		goto __CLEAN_UP;
	}

	PyObject *PyResult = PyObject_CallObject(pGetInformationFunc, NULL);
	if (NULL != PyResult)
	{
		PyObject *pVulNameObj = PyTuple_GetItem(PyResult, 0);
		PyObject *pVulLevelObj = PyTuple_GetItem(PyResult, 1);

		//获取漏洞的名称信息
		int nStrSize = 0;
		LPTSTR pVulName = PyUnicode_AsWideCharString(pVulNameObj, &nStrSize);
		StringCchCopy(lpVulName, MAX_PATH, pVulName);
		PyMem_Free(pVulName);

		//获取漏洞的危险等级
		level = PyLong_AsLong(pVulLevelObj);

		Py_DECREF(pVulNameObj);
		Py_DECREF(pVulLevelObj);
	}

	//解除Python对象的引用, 以便Python进行垃圾回收
__CLEAN_UP:
	Py_DECREF(pModule);
	Py_DECREF(pGetInformationFunc);
	Py_DECREF(PyResult);

}

在示例中调用了一个叫 getInformation 的函数,这个函数的定义如下:

def getInformation():
    return "测试脚本", 1

下面是一个需要传入参数的函数调用

BOOL CallScanMethod(IN LPPYTHON_MODULES_DATA pPyModule, IN LPCTSTR lpUrl, IN LPCTSTR lpRequestMethod, OUT LPTSTR lpHasVulUrl, int BuffSize)
{
	USES_CONVERSION;
	//加载模块
	PyObject* pModule = PyImport_ImportModule(T2A(pPyModule->szModuleName));
	if (NULL == pModule)
	{
		g_OutputString(_T("加载模块[%s]失败!!!"), pPyModule->szModuleName);
		return FALSE;
	}

	//加载模块
	PyObject *pyScanMethod = PyObject_GetAttrString(pModule, "startScan");
	if (NULL == pyScanMethod || !PyCallable_Check(pyScanMethod))
	{
		Py_DECREF(pModule);
		g_OutputString(_T("加载函数[%s]失败!!!"), _T("startScan"));
		return FALSE;
	}

	//加载参数
	PyObject* pArgs = Py_BuildValue("ss", T2A(lpUrl), T2A(lpRequestMethod));

	PyObject *pRes = PyObject_CallObject(pyScanMethod, pArgs);
	Py_DECREF(pArgs);

	if (NULL == pRes)
	{
		g_OutputString(_T("调用函数[%s]失败!!!!"), _T("startScan"));
		return FALSE;
	}

	//如果是元组,那么Python脚本返回的是两个参数,证明发现漏洞
	if (PyTuple_Check(pRes))
	{
		PyObject* pHasVul = PyTuple_GetItem(pRes, 0);
		long bHasVul = PyLong_AsLong(pHasVul);
		Py_DECREF(pHasVul);

		if (bHasVul != 0)
		{
			PyObject* pyUrl = PyTuple_GetItem(pRes, 1);
			int nSize = 0;
			LPWSTR pszUrl = PyUnicode_AsWideCharString(pyUrl, &nSize);
			Py_DECREF(pyUrl);

			StringCchCopy(lpHasVulUrl, BuffSize, pszUrl);
			PyMem_Free(pszUrl);

			return TRUE;
		}
	}

	Py_DECREF(pRes);
	return FALSE;
}

对应的Python函数如下:

def startScan(url, method):
    if(method == "GET"):
	    response = requests.get(url)
    else:
        response = requests.post(url)

    if response.status_code == 200:
        return True, url
    else:
        return False

四、C++数据类型与Python对象的相互转化

Python与C++结合的一个关键的内容就是C++与Python数据类型的相互转化,针对这个问题Python提供了一系列的函数。

这些函数的格式为PyXXX_AsXXX 或者PyXXX_FromXXX,一般带有As的是将Python对象转化为C++数据类型的,而带有From的是将C++对象转化为Python,Py前面的XXX表示的是Python中的数据类型。比如 PyUnicode_AsWideCharString 是将Python中的字符串转化为C++中宽字符,而 Pyunicode_FromWideChar 是将C++的字符串转化为Python中的字符串。这里需要注意一个问题就是Python3废除了在2中的普通的字符串,它将所有字符串都当做Unicode了,所以在调用3的时候需要将所有字符串转化为Unicode的形式而不是像之前那样转化为String。具体的转化类型请参考Python官方的说明。

上面介绍了基本数据类型的转化,除了这些Python中也有一些容器类型的数据,比如元组,字典等等。下面主要说说元组的操作。元组算是比较重要的操作,因为在调用函数的时候需要元组传参并且需要解析以便获取元组中的值。

  1. 创建Python的元组对象

创建元组对象可以使用 PyTuple_New 来创建一个元组的对象,这个函数需要一个参数用来表示元组中对象的个数。

之后需要创建对应的Python对象,可以使用前面说的那些转化函数来创建普通Python对象,然后调用 PyTuple_SetItem 来设置元组中数据的内容,函数需要三个参数,分别是元组对象的指针,元组中的索引和对应的数据

示例:

PyObject* args = PyTuple_New(2);   // 2个参数
  PyObject* arg1 = PyInt_FromLong(4);    // 参数一设为4
  PyObject* arg2 = PyInt_FromLong(3);    // 参数二设为3
  PyTuple_SetItem(args, 0, arg1);
  PyTuple_SetItem(args, 1, arg2);

或者如果元组中都是简单数据类型,可以直接使用 PyObject* args = Py_BuildValue(4, 3); 这种方式来创建元组

  1. 解析元组

Python 函数返回的是元组,在C++中需要进行对应的解析,我们可以使用 PyTuple_GetItem 来获取元组中的数据成员,这个函数返回PyObject 的指针,之后再使用对应的转化函数将Python对象转化成C++数据类型即可

PyObject *pVulNameObj = PyTuple_GetItem(PyResult, 0);
PyObject *pVulLevelObj = PyTuple_GetItem(PyResult, 1);

//获取漏洞的名称信息
int nStrSize = 0;
LPTSTR pVulName = PyUnicode_AsWideCharString(pVulNameObj, &nStrSize);
StringCchCopy(lpVulName, MAX_PATH, pVulName);
PyMem_Free(pVulName); //释放由PyUnicode_AsWideCharString分配出来的内存

//获取漏洞的危险等级
level = PyLong_AsLong(pVulLevelObj);

//最后别忘了将Python对象解引用
Py_DECREF(pVulNameObj);
Py_DECREF(pVulLevelObj);
Py_DECREF(PyResult);

Python中针对具体数据类型操作的函数一般是以Py开头,后面跟上具体的数据类型的名称,比如操作元组的PyTuple系列函数和操作列表的PyList系列函数,后面如果想操作对应的数据类型只需要去官网搜索对应的名称即可。

在C++中调用Python函数需要使用Python C API。以下是一个简单的例子,展示如何在C++中调用Python函数。

首先,确保你的系统上安装了Python,并且你的C++编译器能够找到Python头文件和库。

 
cpp#include <Python.h>

int main() {
// 初始化Python解释器
Py_Initialize();

// 添加当前目录到sys.path,以便可以导入本地Python脚本
PyObject* sysPath = PySys_GetObject("path");
PyList_Append(sysPath, PyUnicode_FromString("."));

// 导入Python模块
PyObject* pModule = PyImport_ImportModule("python_script"); // 假设有一个名为python_script.py的文件
if (!pModule) {
PyErr_Print();
return 1;
}

// 获取模块中的函数
PyObject* pFunc = PyObject_GetAttrString(pModule, "my_function"); // 假设python_script.py中有一个名为my_function的函数
if (!pFunc || !PyCallable_Check(pFunc)) {
PyErr_Print();
return 1;
}

// 调用Python函数,这里没有参数,如果有参数,可以使用PyTuple_Pack等函数来创建参数元组
PyObject* pValue = PyObject_CallObject(pFunc, nullptr);
if (!pValue) {
PyErr_Print();
return 1;
}

// 打印Python函数返回值
printf("Return of my_function: %ld\n", PyLong_AsLong(pValue));

// 释放资源
Py_DECREF(pFunc);
Py_DECREF(pModule);
Py_Finalize();

return 0;
}

确保你有一个名为python_script.py的Python脚本,其中定义了一个名为my_function的函数。

 
python# python_script.py
def my_function():
return 42

编译时,确保链接了Python库,例如使用g++:

 
bashg++ -o call_python call_python.cpp -I/usr/include/python3.x -lpython3.x

替换3.x为你的Python版本。

这个例子展示了如何初始化Python解释器,导入Python模块和调用Python函数。记得在实际应用中处理好内存管理和错误检查。

 // 单链表节点的结构
 public class ListNode {
     int val;
     ListNode next;
     ListNode(int x) { val = x; }
 }

链表

  1. 链表是以节点的方式来存储,是链式存储
  2. 每个节点包含data 域,next域:指向下一个节点.
  3. 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定.

基础知识

哈希表的简单介绍

  1. 哈希表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用HashSet结构(C++中叫UnOrderedSet)
  3. 如果既有key,又有伴随数据value,可以使用HashMap结构(C++中叫UnOrderedMap)
  4. 有无伴随数据,是HashMap和HashSet唯一的区别,底层的实际结构是一回事
  5. 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
  6. 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  7. 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西内存地 址的大小
在java中 HashSet就是一个只使用HashMap的key的一个集合。

有序表的简单介绍

  1. 有序表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用TreeSet结构(C++中叫OrderedSet)
  3. 如果既有key,又有伴随数据value,可以使用TreeMap结构(C++中叫OrderedMap)
  4. 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
  5. 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
  6. 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现 不同
  7. 放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  8. 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占 用是这个东西内存地址的大小
  9. 不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复 杂度

有序表的固定操作:

  1. void put(K key, V value):将一个(key,value)记录加入到表中,或者将key的记录 更新成value。
  2. V get(K key):根据给定的key,查询value并返回。
  3. void remove(K key):移除key的记录。
  4. boolean containsKey(K key):询问是否有关于key的记录。
  5. K firstKey():返回所有键值的排序结果中,最左(最小)的那个。
  6. K lastKey():返回所有键值的排序结果中,最右(最大)的那个。
  7. K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中, key的前一个。
  8. K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的后一个。 以上所有操作时间复杂度都是O(logN),N为有序表含有的记录数

练习

反转单链表和双链表

节点结构:

 public class TreeNode {
     public int num;
     public TreeNode next;
     public TreeNode last;
     public TreeNode(int num) {
         this.num = num;
     }
     public TreeNode() {}
 }
 public class Node {
     public int num;
     public Node next;
     public Node(int num, Node next) {
         this.num = num;
         this.next = next;
     }
 }

两种方式反转链表结构:

 /**
      * 反转单向链表
      * while循环
      */
     public static Node reversal1(Node head) {
         Node last = head.next;
         head.next = null;
         while (last != null) {
             Node temp = last.next;
             last.next = head;
             head = last;
             last = temp;
         }
         return head;
     }
 ​
     /**
      * 反转单链表
      * 递归
      * @param head
      * @return
      */
     public static Node reversal2(Node head) {
         if(head.next == null) {
             return head;
         }
         Node last = reversal2(head.next);
         head.next.next = head;
         head.next = null;
         return last;
     }
 ​
 ​
     /**
      * 反转双向链表
      * 循环
      * @param head
      * @return
      */
     public static TreeNode reversal3(TreeNode head) {
         TreeNode next = head.next;
         head.next = null;
         while (next != null) {
             head.last = next;
             TreeNode temp = next.next;
             next.next = head;
             head = next;
             next = temp;
         }
         return head;
     }
     /**
      * 反转双向链表
      * 递归
      * @param head
      * @return
      */
     public static TreeNode reversal4(TreeNode head) {
         if (head.next == null) {
             return head;
         }
         TreeNode node = reversal4(head.next);
         head.next.last = head.next.next;
         head.next.next = head;
         head.next = null;
         return node;
     }
 ​

打印两个有序链表的公共部分

 public static void portion(Node n1, Node n2) {
         while(n1 != null && n2 != null) {
             if (n1.num == n2.num) {
                 System.out.print(n1.num);
                 n1 = n1.next;
                 n2 = n2.next;
             }else if (n1.num < n2.num) {
                 n1 = n1.next;
             }else if (n1.num > n2.num) {
                 n2 = n2.next;
             }
         }
     }

技巧

面试时链表解题的方法论

  1. 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
  2. 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法

重要技巧:

  1. 额外数据结构记录(哈希表等)
  2. 快慢指针

示例

判断回文链表

 /**
      * 不使用额外的空间,适用于面试
      * @param head
      * @return
      */
     public static boolean plalindrome2(Node head) {
         //首先通过快慢指针找到中间的节点,如果是偶数:中间左边,奇数:中间节点
         Node slowNode = head;
         Node quickNode  = head;
         while(quickNode.next!= null && quickNode.next.next != null) {
             slowNode = slowNode.next;
             quickNode = quickNode.next.next;
         }
         //此时慢指针,就到了中间位置,那么在此后面的的节点 指针反转
         quickNode = slowNode.next;
         slowNode.next = null;
         while (quickNode != null) {
             Node temp = quickNode.next;
             quickNode.next = slowNode;
             slowNode = quickNode;
             quickNode = temp;
         }
         while (head.next != null && slowNode.next != null) {
             if (head.num != slowNode.num) {
                 return false;
             }
             head = head.next;
             slowNode = slowNode.next;
         }
         return true;
     }
 ​
 ​
     /**
      * 使用额外的数据结构,空间复杂度高,适用于笔试
      * @param head
      * @return
      */
     public static boolean plalindrome1(Node head) {
         Stack<Node> stack = new Stack();
         Node node = head;
         while (node != null) {
             stack.add(node);
             node =  node.next;
         }
         while (head != null) {
             if (head.num != stack.pop().num) {
                 return false;
             }
             head = head.next;
         }
         return true;
     }
 ​

将单向链表按某值划分成左边小、中间相等、右边大的形式

【题目】给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。

 /**
      * 使用有限的变量解答题目
      * @param node
      * @param num
      * @return
      */
     private static Node pivot1(Node node, int num) {
         Node ssNode = null;
         Node seNode = null;
 ​
         Node msNode = null;
         Node meNode = null;
 ​
         Node bsNode = null;
         Node beNode = null;
 ​
         while (node != null) {
             if (node.num < num) {
                 if (ssNode != null) {
                     seNode.next = node;
                     seNode = node;
                 }else {
                     ssNode = node;
                     seNode = node;
                 }
             }else if (node.num > num) {
                 if (bsNode != null) {
                     beNode.next = node;
                     beNode = node;
                 }else {
                     bsNode =node;
                     beNode = node;
                 }
             }else if (node.num == num){
                 if (msNode != null) {
                     meNode.next = node;
                     meNode = node;
                 }else {
                     msNode = node;
                     meNode = node;
                 }
             }
             node = node.next;
         }
 ​
         if (ssNode == null) {
             if (meNode == null) {
                 ssNode = bsNode;
             }else {
                 meNode.next= bsNode;
                 ssNode = msNode;
             }
         }else {
             if (meNode == null) {
                 seNode.next = bsNode;
             }else {
                 seNode.next = msNode;
                 meNode.next = bsNode;
             }
         }
         return ssNode;
     }
 ​
     /**
      * 采用数组 的结构 然后利用快速排序的思路,进行处理
      * @param node
      * @param num
      */
     private static void pivot(Node node, int num) {
         Node[] nodes = new Node[6];
         int i = 0;
         while (node != null) {
             nodes[i++] = node;
             node = node.next;
         }
         System.out.println(Arrays.toString(nodes));
         int start = 0, end = nodes.length-1;
         for (int j = 0; j <= end; j++) {
             if (nodes[j].num < num) {
                 Node temp = nodes[j];
                 nodes[j] = nodes[start];
                 nodes[start] = temp;
                 start++;
             }else if (nodes[j].num > num) {
                 Node temp = nodes[j];
                 nodes[j] = nodes[end];
                 nodes[end] = temp;
                 j--;
                 end --;
             }
         }
         System.out.println(Arrays.toString(nodes));
     }
 ​

复制含有随机指针节点的链表

【题目】一种特殊的单链表节点类描述如下

 class Node {
 int value;
 Node next;
 Node rand;
 Node(int val) {
 value = val;
 }
 }

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节 点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。 【要求】时间复杂度O(N),额外空间复杂度O(1)

 /**
      * 使用 Map数据结构 
      * @param head
      * @return
      */
     public static ListNode copy(ListNode head) {
         Map<ListNode, ListNode> map = new HashMap<>();
         ListNode node = head;
 ​
         while (node != null) {
             map.put(node, new ListNode(node.value));
             node = node.next;
         }
         ListNode hListNode = head;
         while (head != null) {
             map.get(head).next = map.get(head.next);
             map.get(head).rand = map.get(head.rand);
             head = head.next;
         }
         return map.get(hListNode);
     }
 ​
 ​
     /**
      *  不使用额外空间 
      * @param head
      * @return
      */
     public static ListNode copy1(ListNode head) {
         ListNode node = head;
         ListNode temp;
         while (node != null) {
             temp = node.next;
             node.next = new ListNode(node.value);
             node.next.next = temp;
             node = temp;
         }
 ​
         node = head;
         while (node != null) {
             temp = node.next;
             if (node.rand == null) {
                 temp.rand = null;
             }else {
                 temp.rand = node.rand.next;
             }
             node = node.next.next;
         }
         return head.next;
     }
 ​

两个单链表相交的一系列问题

【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2。请实 现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返 回null 【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

 //没做

递归反转单链表

 public ListNode reverse(ListNode head) {
         if (head.next == null) {
             return head;
         }
         ListNode lastListNode = reverse(head.next);
         head.next.next = head;
         head.next = null;
         return lastListNode;
     }

递归反转前n个节点

 ListNode successor = null; // 后驱节点
 ​
 // 反转以 head 为起点的 n 个节点,返回新的头结点
 public ListNode reverseN(ListNode head, int n) {
     if (n == 1) { 
         // 记录第 n + 1 个节点
         successor = head.next;
         return head;
     }
     // 以 head.next 为起点,需要反转前 n - 1 个节点
     ListNode last = reverseN(head.next, n - 1);
 ​
     head.next.next = head;
     // 让反转之后的 head 节点和后面的节点连起来
     head.next = successor;
     return last;
 }

递归反转指定范围的节点

 ListNode reverseBetween(ListNode head, int m, int n) {
     // base case
     if (m == 1) {
         return reverseN(head, n);
     }
     // 前进到反转的起点触发 base case
     head.next = reverseBetween(head.next, m - 1, n - 1);
     return head;
 }

递归的思想相对迭代思想,稍微有点难以理解,处理的技巧是:不要跳进递归,而是利用明确的定义来实现算法逻辑。

https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/shou-ba-shou-shua-lian-biao-ti-mu-xun-lian-di-gui-si-wei/di-gui-fan-zhuan-lian-biao-de-yi-bu-fen

leetcode

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
     public ListNode success1 = null;
     public ListNode reverseBetween(ListNode head, int left, int right) {
         if (left == 1) {
             return reverseBetween(head, right);
         }
         head.next = reverseBetween(head.next, --left, --right);
         return head;
     }
 ​
     public ListNode reverseBetween(ListNode head, int n) {
         if (n == 1) {
             success1 = head.next;
             return head;
         }
         ListNode laListNode = reverseBetween(head.next, --n);
         head.next.next = head;
         head.next = success1;
         return laListNode;
     }
 ​
 }

k个一组反转链表

我的代码:

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
          ListNode[] arr = reverse(head, k);
          ListNode preListNode = arr[2];
          ListNode preListNode1 = arr[0];
          while (arr[1] != null) {
              if (!judge(arr[1], k)) {
                 break;
             }
             arr = reverse(arr[1], k);
             preListNode.next = arr[0];
             preListNode = arr[2];
         }
          return preListNode1;
      }
      public boolean judge(ListNode head, int k) {
          ListNode node = head;   
          while (node != null) {
             node = node.next;
             k--;
             if (k == 0) {
                 return true;
             }
         }
          return false;
      }
      
      public ListNode[] reverse(ListNode head, int k) {
          ListNode[] arr = new ListNode[3];
          ListNode preNode = head;
          ListNode currentNode = head.next;
          ListNode tempListNode = null;
          while (currentNode != null&&k>1) {
             tempListNode = currentNode.next;
             currentNode.next = preNode;
             preNode = currentNode;
             currentNode = tempListNode;
             if (k == 2) {
                 break;
             }
             --k;
         }
         head.next = currentNode;
         arr[0] = preNode;
         arr[1] = currentNode;
         arr[2] = head;
         return arr;
      }
 ​
 }

拉布拉多

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
          if (head == null) {
             return null;
         }
          ListNode a,b;
          a = head;
          b = head;
          for (int i = 0; i < k; i++) {
              if (b == null) {
                 return head;
             }
             b = b.next;
         }
         // 反转前 k 个元素
         ListNode newHead = reverse(a, b);
         // 递归反转后续链表并连接起来
         a.next = reverseKGroup(b, k);
 ​
         return newHead; 
      }
     public ListNode reverse(ListNode a, ListNode b) {
         ListNode pre,cur,temp;
         pre = a;
         cur = a.next;
         while (cur != b) {
             temp = cur.next;
             cur.next = pre;
             pre = cur;
             cur = temp;
         }
         return pre;
     }
 ​
 }

回文链表

方式一:

空间复杂度为O(1)的

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
     public boolean isPalindrome(ListNode head) {
         ListNode a = head;
         ListNode b = head;
         while (b.next != null && b.next.next != null) {
             b = b.next.next;
             a = a.next;
         }
         ListNode c;
         c = a.next;
         a.next = null;
         while (c != null) {
             b = c.next;
             c.next = a;
             a = c;
             c = b;
         }
         c = a;
         b = head;
         while (b != null && a != null) {
             if (b.val != a.val) {
                 return false;
             }
             b = b.next;
             a = a.next;
         }
         //复原
   
         return true;
     }
 }

方式二:

使用栈

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
         Stack<Integer> stack = new Stack<>();
		ListNode node  = head;
		while (node != null) {
			stack.add(node.val);
			node = node.next;
		}
		while (head != null) {
			if (stack.pop() != head.val) {
				return false;
			}
			head = head.next;
		}
		return true;
    }
}

方式三:

使用递归

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
   ListNode left;
	public boolean isPalindrome(ListNode head) {
		left = head;
		return traverse(head);
   }

	public boolean traverse(ListNode head) {
	    // 前序遍历代码
	    if (head == null) {
			return true;
		}
	  
	    // 后序遍历代码
	    boolean res =  traverse(head.next);
	    boolean b = res && head.val == left.val;
	    left = left.next;
	    return b;
	}
}

面试题 02.02. 回倒数第 k 个节点

//双指针
public int kthToLast(ListNode head, int k) {
       ListNode n1 = head;
		ListNode n2 = head;

		while (k != 0) {
			n1 = n1.next;
			k--;
		}
		while (n1 != null) {
			n1 = n1.next;
			n2 = n2.next;
		}

		return n2.val;
    }
```python
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # We "pool" the model by simply taking the hidden state corresponding
        # to the first token.
        first_token_tensor = hidden_states[:, 0]
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output
from transformers.models.bert.configuration_bert import *
import torch
config = BertConfig.from_pretrained("bert-base-uncased")
bert_pooler = BertPooler(config=config)
print("input to bert pooler size: {}".format(config.hidden_size))
batch_size = 1
seq_len = 2
hidden_size = 768
x = torch.rand(batch_size, seq_len, hidden_size)
y = bert_pooler(x)
print(y.size())
```

  • 41
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值